A Google Maps Store Locator in Only Javascript
* UPDATE: I’ve re-worked this code and have created a jQuery plugin based on it.
Recently, I was tasked with creating a “store” locator for a local financial institution that has several branches in the Twin Cities area. The problem was that there wasn’t a chance I’d receive access to their server or database and would need to make a special request if I needed access to a server-side programming language. Because of a tight deadline, I decided not to bother working with a server-side language or a database and just create the locator with only Javascript. I knew I wanted to use the Google Maps API and figured there would be at least one example out there I could work off of. However, my presumption was incorrect and I ended up creating it myself along with some helpful snippets of code I came across along the way. It wasn’t an overly complex thing to complete but I figured I’d post it so that anyone else looking to do the same has an example to follow. I made the example very thorough in case you aren’t familiar with the Google Maps API or jQuery.
The locator that I ended up with used an XML file to store all of the location data, which I changed to local Chipotle locations for this example (there are quite a few in the area). I figured that because there weren’t more than 20 branches, I might as well just create the XML by hand and have all the geocoding done with the latitude and longitude in the file. There are obviously pros and cons to this and I wouldn’t recommend it if you have more than 20 or so locations. The speed is nice with everything pre-entered and geocoded and there really aren’t any security concerns, but it takes some time to type everything out. The code I came up with geocodes only the user’s location input but it could definitely be modified to geocode all the store locations as well if needed.
Note that because I’m familiar with Version 2 of the Google Maps API, I decided to use that instead of Version 3 so that I could quickly finish it. I may update this post with a Version 3 example at some point in the future.
I started by going to the Google Maps API site and looking at documentation for a PHP/MySQL example. The first portion this example just discusses how to create the XML file with PHP so, because I was already had the file completed and wasn’t looking to make one dynamically, I skipped down to the portion after “Checking that XML output works.” They give a useful example of what the XML should look like:
<markers> <marker name="Pan Africa Market" address="1521 1st Ave, Seattle, WA" lat="47.608940" lng="-122.340141" type="restaurant"/> <marker name="Buddha Thai & Bar" address="2222 2nd Ave, Seattle, WA" lat="47.613590" lng="-122.344391" type="bar"/> <marker name="The Melting Pot" address="14 Mercer St, Seattle, WA" lat="47.624561" lng="-122.356445" type="restaurant"/> <marker name="Ipanema Grill" address="1225 1st Ave, Seattle, WA" lat="47.606365" lng="-122.337654" type="restaurant"/> <marker name="Sake House" address="2230 1st Ave, Seattle, WA" lat="47.612823" lng="-122.345673" type="bar"/> </markers>
The XML file I created by hand is at the following page.
https://www.bjornblog.com/map/locations.xml
The rest of the documentation is what I used as a guideline and the API reference page also came in handy. My first step was to load the XML file. Because jQuery can easily parse XML, and because I knew I was going to be using jQuery for some other things, I decided to include the library. jQuery is amazing and I’m using it on almost every new project. It’s easy to learn and I highly recommend it. Anyways, onto the parsing:
//Parse xml with jQuery $.ajax({ type: "GET", url: "locations.xml", dataType: "xml", success: function(xml) {
After the XML was loaded, all the data needed to be sucked out. I decided to use jQuery’s each() function to loop through the data and create a multi-dimensional array for each location.
var i = 0; $(xml).find('marker').each(function(){ //Take the lat lng from the user, geocoded above var lat = $(this).attr('lat'); var lng = $(this).attr('lng'); var name = $(this).attr('name'); var address = $(this).attr('address'); var address2 = $(this).attr('address2'); var city = $(this).attr('city'); var state = $(this).attr('state'); var postal = $(this).attr('postal'); var distance = GeoCodeCalc.CalcDistance(orig_lat,orig_lng,lat,lng, GeoCodeCalc.EarthRadiusInMiles); //Create the array locationset[i] = new Array (distance, name, lat, lng, address, address2, city, state, postal); i++; });
Most of the code above should be straightforward. I’m taking the data from the XML file, assigning it do different variables, and creating the array. The one thing that you’re probably wondering about though is the GeoCodeCalc.CalcDistance function. I needed to find a way to calculate the distance from the user’s location to all of the company locations/branches. After a quick Google search I found a calculation script that seems to work perfectly on Chris Pietschmann’s blog. I used that exact code at the top of the file:
var GeoCodeCalc = {}; GeoCodeCalc.EarthRadiusInMiles = 3956.0; GeoCodeCalc.EarthRadiusInKilometers = 6367.0; GeoCodeCalc.ToRadian = function(v) { return v * (Math.PI / 180);}; GeoCodeCalc.DiffRadian = function(v1, v2) { return GeoCodeCalc.ToRadian(v2) - GeoCodeCalc.ToRadian(v1); }; GeoCodeCalc.CalcDistance = function(lat1, lng1, lat2, lng2, radius) { return radius * 2 * Math.asin( Math.min(1, Math.sqrt( ( Math.pow(Math.sin((GeoCodeCalc.DiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.cos(GeoCodeCalc.ToRadian(lat1)) * Math.cos(GeoCodeCalc.ToRadian(lat2)) * Math.pow(Math.sin((GeoCodeCalc.DiffRadian(lng1, lng2)) / 2.0), 2.0) ) ) ) ); };
The next step in the Google documentation moves on to creating the map but before that I needed to create a sorting function. Each of the locations in the multi-dimensional array have to be sorted by the distance from the user’s location. Sorting a multi-dimensional array seemed difficult at first but a co-worker pointed me in the right direction with the following simple function:
//Sort the multi-dimensional array numerically locationset.sort(function(a, b) { var x = a[0]; var y = b[0]; return ((x < y) ? -1 : ((x > y) ? 1 : 0)); });
Next, I needed to actually create the map. Google still uses the body onload() function in its Google Maps tutorials but since I was already including jQuery there’s a better way to handle it. Fellow Minnesotan developer Marc Grabanski has a great tutorial on his blog. I created the map using this method and load it into an empty div with an id of “map” that’s in the HTML body. The orig_lat and orig_lng variables are defined by the user’s input, which I worked on last (I used a set latitude and longitude for testing while setting everything else up) – I’ll cover that at the end.
//Create the map with jQuery $(function(){ var map = new GMap2(document.getElementById('map')); map.addControl(new GSmallMapControl()); map.addControl(new GMapTypeControl()); var center_location = new GLatLng(orig_lat,orig_lng); map.setCenter(center_location, 11); // Create a base icon for all of our markers that specifies the shadow, icon dimensions, etc. var letter; var baseIcon = new GIcon(); baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png"; baseIcon.iconSize = new GSize(20, 34); baseIcon.shadowSize = new GSize(37, 34); baseIcon.iconAnchor = new GPoint(9, 34); baseIcon.infoWindowAnchor = new GPoint(9, 2); baseIcon.infoShadowAnchor = new GPoint(18, 25);
After the basic map is set up the markers need to be added. I created a simple for loop that goes through the locationset multi-dimensional array and creates the markers with the latitude and longitude of each location. At the end of the loop the markers are put into an array.
var markers = new Array(); for (var y = 0; y <= locationset.length-1; y++) { var letter = String.fromCharCode("A".charCodeAt(0) + y); var point = new GLatLng(locationset[y][2], locationset[y][3]); marker = createMarker(point, locationset[y][1], locationset[y][4], letter); map.addOverlay(marker); markers[y] = marker; }
Once the map and markers were sorted out, I wanted to create an ordered list of locations with the location name, address, etc. In the body HTML code I created an empty alphabetical ordered list with a containing div and another container div around both the list and map (I ended up putting the entire thing inside of a colorbox for the financial institution). Back to the JS, I first make sure the list is empty, with jQuery’s empty() function, to make sure that the list resets if someone enters a new location after already submitting the form. I then used jQuery’s each() function to loop through and create a list item with the proper information for each marker that exists. I wanted to have the map center on the location if a user clicks on one of the list items so I used jQuery’s click() function, which points to a fairly standard Google Maps displayPoint function. The list is then appended to the empty ordered list in the body. The last small function still within the each() iteration makes it so that the map centers on a marker when you click a marker on the map.
//Creat the links that focus on the related marker $("#list").empty(); $(markers).each(function(y,marker){ //HTML List to appear next to the map $('<li />').html("<div class=\"loc-name\">" + locationset[y][1] + "<\/div> <div class=\"loc-addr\">" + locationset[y][4] + "<\/div> <div class=\"loc-addr2\">" + locationset[y][5] + "<\/div> <div class=\"loc-addr3\">" + locationset[y][6] + ", " + locationset[y][7] + " " + locationset[y][8] + "<\/div>").click(function(){ displayPoint(marker, y); }).appendTo("#list"); GEvent.addListener(marker,"click", function(){ map.panTo(marker.getLatLng()); }); });
The displayPoint function:
//Move the map function displayPoint(marker, index){ var moveEnd = GEvent.addListener(map,"moveend", function(){ var markerOffset = map.fromLatLngToDivPixel(marker.getLatLng()); GEvent.removeListener(moveEnd); }); map.panTo(marker.getLatLng()); }
The next one line function alternates the list item background color. I occasionally use the jQuery alternate plugin to do this. It probably isn’t necessary, as there are multiple ways to accomplish this, but it just makes things easy.
//Alternate the list colors $('#list li').alternate();
The last function in the main mapping function creates the custom icon based on the baseIcon that was previously set up. It is basically just the standard marker with a letter added.
//Custom marker function - alphabetical function createMarker(point, name, address, letter) { var letteredIcon = new GIcon(baseIcon); letteredIcon.image = "http://www.google.com/mapfiles/marker" + letter + ".png"; markerOptions = { icon:letteredIcon }; var marker = new GMarker(point, markerOptions); return marker; }
Once I had all this done and tested with a set latitude and longitude I needed to actually create the HTML form to allow users to input their location. In the body HTML you’ll see the code for the very basic form. I’m didn’t use a standard submit button mostly out of habit. In most cases it’s easy for a bot to take advantage of an HTML form and even though it really doesn’t matter for this functionality (since it only sends a string of data to Google). I prefer to do form submissions this way when I’m not working with a CMS. This method also makes some basic validation with JS easy.
So, back at the top of the file, you’ll see a function that handles the input. I used the jQuery serialize() function to capture the data that is submitted. I then used standard JS to replace the “address=” which is taken from the input name with the serialize function. I then did a basic check for blank input and the GoogleGeocode function geocodes the data. In the next few lines, I set the geocoded input to the olat and olng variables and then run the main mapping function. At first I didn’t separate the input from the rest of the mapping and I would get errors because the mapping would finish before the input was geocoded.
//Process form input $(function() { $('#submit-btn').click(function() { var userinput = $('form').serialize(); userinput = userinput.replace("address=",""); if (userinput == "") { alert("The input box was blank."); } var g = new GoogleGeocode(apiKey); var address = userinput; g.geocode(address, function(data) { if(data != null) { olat = data.latitude; olng = data.longitude; mapping(olat, olng); } else { //Unable to geocode alert('ERROR! Unable to geocode address'); } }); //Replace spaces in user input userinput = userinput.replace(" ","+"); }); });
To accomplish the geocoding I found a function on Petar Strinic’s blog, which I modified a bit. Basically, all I wanted to be returned from Google was latitude and longitude. All the rest wasn’t necessary because I had that data for all my locations. The modified geocoding code looks like this:
//Do the geocoding function GoogleGeocode(apiKey) { this.apiKey = apiKey; this.geocode = function(address, callbackFunction) { $.ajax({ dataType: 'jsonp', url: 'http://maps.google.com/maps/geo?output=json&oe=utf8&sensor=true' + '&key=' + this.apiKey + '&q=' + address, cache: false, success: function(data){ if(data.Status.code==200) { var result = {}; result.longitude = data.Placemark[0].Point.coordinates[0]; result.latitude = data.Placemark[0].Point.coordinates[1]; callbackFunction(result); } else { callbackFunction(null); } } }); }; }
I think that covers just about everything. You’ll notice in the center of the mapping function a block that is commented out. I used this simply for testing – it basically appends all the data from the locationset array in an HTML block. This was useful when I was looking at the calculation numbers and to make sure the markers were corresponding with the correct set of data in the multi-dimensional array. Feel free to use, expand, or improve. I’ll try to answer questions if you have them.
View the entire JS file
View a demo
Download the demo (the Google Maps API key needs to be updated in 2 spots in the index.html file)
Thank you very much!!!!! Your code helped me very much!!! I’ll show you what I’m doing very soon, once it’s finished
Cheers,
Eduardo
Hello, I am developing an iphone application for my company. In this application, there is a store locator function. And I did PHP which connect mysql in my host. But I figured out phonegap cannot deploy PHP language. Therefore I have to redo store locator by only javascript and Html.
My question is my store locator data is like 1700 stores which contains a lot of data. If use XML manually, it will take a lot of time. I already export my database as a XML file from mysql. but the format is different with yours. How can I modify your codes to fit my XML files?
My XML file format is as following:
CREATE TABLE `markers` (
`account` int(4) default NULL,
`address` varchar(92) default NULL,
`name` varchar(51) default NULL,
`phone` varchar(22) default NULL,
`URL` varchar(85) default NULL,
`lat` decimal(15,6) default NULL,
`lng` varchar(17) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
1453
615 Piikoi Ste 102 Honolulu HI 96814
Party Dress by Claudette
808-592-1811
NULL
21.295213
-157.845541
2703
11330 SW 184 St Miami FL 33157
Rosie’s Bridal LLC
305-252-2633
NULL
25.597850
-80.3756006
1418
9520 SW 40th St Miami FL 33165
Olga’s Bridal
305-552-1284
NULL
25.731588
-80.3893402000000
1024
322 Miracle Mile Coral Gables FL 33134-5819
J Del Olmo Bridal
305-448-3599
NULL
25.749210
-80.259064
Can you please help me?
best regard,
KJ
I don’t know why the codes missing something.
or maybe can you give me your email then I can send u my XML then you can take a look?
best regard,
Did you figure the first part out? You could just create a temporary PHP file to output your location information in the correct format and then just copy the output and create a static XML file with it.
I am actually using this one
http://code.google.com/apis/maps/articles/phpsqlsearch.html
I don’t exactly know how to “create a temporary PHP file to output your location information in the correct format and then just copy the output and create a static XML file with it.”
Can you give me some hint?
it will be very appreciated.
best regard,
Ok, you can use the “Outputting XML with PHP” section on that tutorial page. Just get the XML to output with PHP and instead of including that dynamic file, copy the XML, paste it into an editor, and save it as an XML file.
Hello Bjorn, thanks for the hints….it works….I got all XML file
I have another question, How can I modify your code by searching zip code with radius??
Coz in my XML has 1700 stores. And it just crash if I try to search anything. coz the side bar will show all store what XML has. I actually tried if I just put 10 or 20 store in XML and it works. So I think it just because the XML is too large that’s why it crashed. So I think the best way to solve it is to set searching zip within radius option? Do you have any idea how can I modify your code to that? I tried something but it keep getting errors.
I do really really appreciate your help
best regard,
KJ
It actually should search by zip code or city already. I think what you’re wanting to do is limit the number of results per search. To do that you can change the for loop to only return 15 (or whatever you want) closest markers. Change the locationset.length-1 in “for (var y = 0; y <= locationset.length-1; y++)" to the number you want. Example: for (var y = 0; y <= 14; y++) I didn't test this but I think that should do it.
it works……thanks Bjorn.
I do really appreciate….:)
Anyway to separate the zipcode input box out so that I can put a zipcode input box everywhere on the headers of my site and once a user hits enter it takes them to the page where it lists the results of the search results? Thanks.
Yes, you can use a query string variable like this: http://stackoverflow.com/questions/404891/how-to-pass-values-from-one-page-to-another-in-jquery
Have the user put their zip code in and pass that variable to the next page in the URL. Get the variable and then just change the top of the “Process form input” section above so that it uses that variable.
If you wanted to do something fancier you could use Colorbox (http://colorpowered.com/colorbox/) instead of the “window.location” method in the above example and pop open the map in a pop-up window (look at the Outside HTML or iframe examples).
I’ve tried and still can’t get it to work. Please help.
Could you please help with this? It’s not working for me from the example you’ve given me. The data is not being passed via URL.
Is it possible to set radius in select box to 10km/15 km/20km and so on.
Example : Radius: 5km10km15km
And can we show a tooltip on the markers as well?
Thanks this is great, unfortunately I cant get a v2 API key, I am attempting to port this to the v3 API.
Any assistance would be great.
Thanks
It was great article thanks for the same, this is not working for me when I am using a location not in markers list. Actually I wanted it to work in such a way, if the store is not there in particular pin it need to show nearest locations.
Thanks.
is it possible to have the map showing at all times??
Anyway that we can have a radius drop down of say locations within 25, 50, and 100 miles of a certain zip code? Even with limiting the search results it still displays locations that are way far away.
Thanks!
Hi Bjorn,
Thanks yet another time for a great article.
I have written an API that renders the XML with the relevant stores corresponding to user’s current location. So in my case i do not require the Zip search functionality. What changes do i need to do to just plot all of them in the map.
Thanks in advance,
Mohan, India
API V2 service was shut down by Google, this script does not work, you need a program that is based on API V3
@dirate Well, it wasn’t shut down but you may not be able to get an API key for version 2 anymore (the demo still works). I’m actually almost done with an update to this script with API version 3 and will be adding several addition examples. I’ll probably throw it up on github when it’s done.
I’m sorry I haven’t directly answered some questions here but I don’t have time to make heavy modifications for free. Hopefully my new examples will be helpful.
Hello everyone,
I’ve created a new version of this script using the Goolge Maps API version 3 and turned it into a jQuery plugin. Please see the following URL:
https://www.bjornblog.com/web/jquery-store-locator-plugin
hi , if i wont to display default location to be display when a page is load then what to do ?
Hi vijay, did you see I re-worked this and released it as a jQuery plugin? See comment 28 on this post, which I believe is the same question you’re asking: https://www.bjornblog.com/web/jquery-store-locator-plugin
Thx, alot.
This was exactly wat i was looking for!
Hey Bjorn, first of all thanks for a fantastic tutorial!
I was wondering if you had any tips on including search filters for the maps using check boxes and drop down lists. Similar to the Ace Hardware Store locator. I actually used a mySQL database and passed the data through PHP into XML but im guessing a filtering option can be equally applied to your tutorial?
Many thanks!
Ben
Hi
Great i was looking for smothing like this a long time. I tryed the demo but did not work…
Did you ever try to make one, that geolocates the user, and shows only the nearest store?
I am trying to do it, but my skills in javascript are … us a beginner…, i just have the code to geolocate the user, but the function to search is not working…
Anyway, if you are interested in see the code and even share it i can send it to you.
Thanks
Nelson Parreira
No, this is old and doesn’t work anymore. Please see above – I’ve converted this into a jQuery plugin: https://www.bjornblog.com/web/jquery-store-locator-plugin