Google Maps API: Create a store locator - javascript

Today I am trying to make a store locator using google maps' api.
The store locator is to be set up like so:
two areas, one with a map containing all the stores in a given area (measured in a selectable radius from a center point), and one area with a list of all the stores on the map, their information, and of course a link to their website. When a person clicks on the name of the store on the store list, it centers upon the store in the map, and opens an infoWindow above the store marker.
I have a javascript variable to which I have taken pains to assign some json data from a php script (which is selecting this data on the fly from a database)
locations = [{"siteurl":"http:\/\/localhost.localdomain\/5","address":"260 Test St","city":"Brooklyn","state":"New York","zip_code":"11206"},{"siteurl":"http:\/\/localhost.localdomain\/4","address":"3709 Testing St.","city":"Austin","state":"Texas","zip_code":"78705"}];
Now, I know there are 5 different functions I need to run, listed below with their apparent use:
geocoder.getLocations : Used to
convert address data (from the json
object) into latitude and longitude
data object
addElementToList :
Used to add address information to
the list of stores, and bind the
centerOnStore function to onclick
centerOnStore when a store list item is clicked in the list area, this function center's upon the store that has been clicked on in the map area. This function also opens an infoWindow above the centered upon store.
placeMarker the function to place a marker on the map, called once the geocoder returns latitudeLongitude objects
eventListener this is tied up somehow in the clicking of a list item and it's further centering the map upon the store in question
Well, i am out of my league it would appear. I am just now learning about javascript closures, and I think these may be necessary, but I can't quite understand them. I need to figure out some way to get all these functions into a working order, passing information back and forth to each other, and create a store locator
.
Here is what I've got so far, but there is something very wrong with it.
var map = null;
var geocoder = null;
var locations = null;
var center_on = null;
var zoom_level = null;
var markerList = [];
function initialize()
{
if(GBrowserIsCompatible())
{
// Assign vars
map = new GMap2(document.getElementById("map_canvas"));
geocoder = new GClientGeocoder();
locations = <?php echo(json_encode($my_vars['locations'])); ?>;
center_on = "<?php echo($my_vars['center_on']); ?>";
zoom_level = <?php echo($my_vars['zoom_level']); ?>;
var currentLocation = 0;
geocoder.getLatLng(center_on, function(myPoint)
{
map.setCenter(myPoint, zoom_level);
});
map.setUIToDefault();
var list = document.getElementById('center_list');
for(var i = 0; i < locations.length; i++)
{
var address = locations[i]['address'] + ', ' + locations[i]['city'] + ' ' + locations[i]['state'] + ', ' + locations[i]['zip_code'];
geocoder.getLocations(address, addAddressToMap);
}
}
function addAddressToMap(response) {
if (!response || response.Status.code != 200) {
currentLocation++;
} else {
var place = response.Placemark[0];
var point = new GLatLng(place.Point.coordinates[1],
place.Point.coordinates[0]);
marker = new GMarker(point);
GEvent.addListener(marker, 'click', function(){
this.openInfoWindowHtml("<strong>" + place.address + "</strong><br /><a href='" + locations[currentLocation]['siteurl'] + "'>" + locations[currentLocation]['siteurl'] + "</a>");
});
map.setCenter(point, 13);
markerList.push(marker);
map.addOverlay(marker);
li = document.createElement('li');
li.innerHTML = "<strong>" + place.address + "</strong>";
li.setAttribute('onclick', 'center_on_center(' + place.Point.coordinates[1] + ',' + place.Point.coordinates[0] + ')');
li.setAttribute('id', 'center_');
li.style.fontSize = '1.4em';
document.getElementById('center_list').appendChild(li);
// alert(currentLocation) here says 0,0,0,0
currentLocation++;
// alert(currentLocation) here says 1,2,3,4
}
}
}
I am sorry for the wall of code. I can't think anymore. I had no idea this would be so difficult. No idea at all.
if I alert currentLocation in the line before I increment it, it's always 0. but If I alert it in the line after I increment it, it's '1,2,3,4' etc. This goes against everything I know about computers.

Forget about closures for a moment. You can dive into those once you get a working app. I think you're goal at this point to should be to just get something that accomplishes what you want.
To me, it seems like the only piece you're missing is the idea of a callback function. For instance, addElementToList would be passed as the callback argument to geocoder.getLocaitons. The way it works is that when getLocations() finishes, it calls addElementToList and supplies the result from getLocations() as an argument to addElementToList. The code for addElementToList will then add your store location to the map as a marker and add a new element to your html list with the store's name or address or whatever.
Take a look at this blog post for a simple example using a callback: Introducing Google's Geocoding Service.
The last part, centering on a specific store, can be done (as you suggested) with event listeners. You can set up a listener for clicks on the markers and also for clicks on your list. When you add a marker, you can also add an event listener on it. It'd be nice if you could set one listener for all markers on the map but I'm not familiar enough with google's API to know if this is possible.

What is your source for that information? placeMarker certainly doesn't ring a bell. The Google Maps API reference (complete with examples!) is available at http://code.google.com/apis/maps/documentation/reference.html

Based on your comment to #Rushyo's answer- it seems like you know enough about Javascript and the Google Maps API to construct those functions. I'm a little confused as to what you're looking for.
I would suggest however, that you add lat/lon coordinates to your database in the first place. You shouldn't have to geocode the addresses every time the map is loaded.
Update: In response to your comment below, here is the code you referenced - along with the addAddressToMap() function called by the Geocoder. It creates a marker for each address and adds it to the array markerList. You can then access the markers in that array later, since we initialized it outside the scope of the addAddressToMap() function.
for(var i = 0; i < locations.length; i++) {
var address = locations[i]['address'] + ', ' + locations[i]['city'] + ' ' + locations[i]['state'] + ', ' + locations[i]['zip_code'];
geocoder.getLocations(address, addAddressToMap);
}
var markerList = new array();
function addAddressToMap(response) {
if (!response || response.Status.code != 200) {
alert("\"" + address + "\" not found");
} else {
place = response.Placemark[0];
point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
marker = new GMarker(point);
markerList.push(marker);
map.addOverlay(marker);
}
}
Update 2: In response to the code you posted in your question above, you're probably getting random numbers in currentLocation because of the asynchronous nature of the Geocoder. Remember that your getLocations() function will send requests for every location in the array before it gets any responses back.

I'm creating a new answer, since my other answer is getting messy.
In order to get proper closure, you'll need to create a separate function to make the geocoder request. The following code will allow you to assign the desired infoWindow text to each marker.
for(var i = 0; i < locations.length; i++) {
var address = locations[i]['address'] + ', ' + locations[i]['city'] + ' ' + locations[i]['state'] + ', ' + locations[i]['zip_code'];
var text = locations[i]['address']; // or whatever you want the text to be
getLocation(address, text);
}
...
function getLocation(address, text) {
geocoder.getLocations(address, function(response) {
var place = response.Placemark[0];
var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
marker = new GMarker(point);
marker.bindInfoWindowHtml(text); // note that if you want to use GEvent.addListener() instead - you'll need to create another function to get proper closure
map.addOverlay(marker);
});
}
For more info on closure in Google maps, see these questions:
One
Two
Three

Related

Javascript Object with a variable

I'm building a basic game with NSEW navigation.
Each NSEW button changes the current location's number, 1,2,3,etc.
Each location has an object that's intended to be associated with it, named loc1,loc2,loc3,etc.
Each object has a description that needs to be displayed, loc1.desc, loc2.desc, etc.
My display function is working, as is my navigation, BUT...
I'm TRYING to pass the loc#.desc value corresponding to the correct current location into the function. (This is Javascript, btw). Currently, it looks like this:
function nextLoc(dir) {
var newLoc = nav[currentLoc][dir];
currentLoc=newLoc;
displayMessage(loc[currentLoc].desc);}
I want it to input the current location's number and pass that to the displayMessage function. I've tried it a ton of different ways, but it still does NOT print the description. If I hard code the number (loc2.desc) or just pass currentLoc, it works, returning the correct object description or the currentLoc number. I've also tried:
loc+[currentLoc]+.desc
Is there a way to do this? I've searched and tried all the different ways to find this but I can't find this specific issue and, at this point, I'm just lost! Any help is greatly appreciated!!
In answer to comments, here's the whole js file:
//Location prototype
function Location(id, desc){
this.id = id;
this.desc = desc;}
//Location objects
var loc2 = new Location(2, "Circus");
var loc1 = new Location (1, "Zoo");
var loc0 = new Location (0,"You entered the park here");
var currentLoc = 0;
var EAST = 0;
var WEST = 1;
var NORTH = 2;
var nav = [ // E,W,N,S
/*Current Location*/
/* 0 */ [2,1,4,-1],
/* 1 */ [0,-1,3,-1],
/* 2 */ [-1,0,5-1],
/* 3 */ [4,-1,-1,1],
/* 4 */ [5,3,-1,0],
/* 5 */ [-1,4,-1,2],
];
// Directional Button Event Handlers
function btnEast_click() {nextLoc(EAST);}
function btnWest_click() {nextLoc(WEST);}
function btnNorth_click() {nextLoc(NORTH);}
function nextLoc(dir) {
var newLoc = nav[currentLoc][dir];
currentLoc=newLoc;
displayMessage(loc[currentLoc].desc);}
// Utility Function(s)
function displayMessage(msg) {
var target = document.getElementById("taMain");
target.value = msg + "\n\n" + target.value;
}
You were quite close to being able to do named-lookups in a map object. Rather than creating a bunch of independent locations (which in a browser, end up as properties of the window object, so there was an avenue that I've chosen not to pursue that would've let you use them.
What I'm doing below is creating an object for the static locations. Another approach would be to use notation like this, which would actually result in the same behavior but might be easier to understand what's going on:
var locations = [];
locations['loc2'] = new Location(2, "Circus");
locations['loc1'] = new Location(1, "Zoo");
locations['loc0'] = new Location(0, "You entered the park here.");
Also workable would be removing the 'loc' prefix on your keys, then you could write things like this:
var locations = [];
locations.add = function(id, desc){ locations[id] = new Location(id, desc)}
locations.add(0, "You entered the park here.")
// and your navigation method looks like this then
function nextLoc(dir){
var newLoc = nav[currentLoc][dir];
currentLoc=newLoc;
displayMessage(locations[currentLoc].desc);
}
Another form which resembles what you've done so far
var locations = {
loc2 : new Location(2, "Circus"),
loc1 : new Location (1, "Zoo"),
loc0 : new Location (0,"You entered the park here")
};
function nextLoc(dir) {
var newLoc = nav[currentLoc][dir];
currentLoc="loc"+newLoc;
displayMessage(locations[currentLoc].desc);}

Dynamically create hrefs that pan to unique Google Map latLng in asynch function

I'm trying to dynamically append hrefs that will, when clicked, pan a Google map to given coordinates. The code below successfully creates href links that pan the map -- but it keeps panning to the location of the most recently created circle.
I added the closure panToCircle, hoping that would preserve the the current coordinates within the enclosed IIFE, but no such luck. What do you think is my best strategy, here? Thanks a ton!
Note: This is all chained from an asynch callback, creating the circle every time a response comes from the server.
var text = 'Hello World!';
// Hoisting the panToCircle up here, as Chrome was giving me undefined ref errors when initializing it in linkToCircle.
var panToCircle = function(){};
var rgb = [100,100,100];
createCircle();
function createCircle(){
var color = 'RGB(' + rgb.toString() + ')';
var myCircle = initCircle();
linkToCircle();
function linkToCircle(){
var a = document.createElement('a');
a.innerHTML = text + "<br><br>";
a.style.color = color;
panToCircle = function(){
// Defining center in here, as putting a center in as an argument to the href function reclasses the center as a string, not a latLng
var center = myCircle.getCenter();
(function panNow(cen){
map.panTo(cen);
})(center);
}
a.href = 'javascript:panToCircle();'
document.getElementById('text-div').appendChild(a);
}
}
Cool, a really simple fix worked, just had to take a step back from it and consider other options. I formatted a string from the current coordinates and concatenated it into the href value.
function linkToCircle(){
var a = document.createElement('a');
a.innerHTML = text + "<br><br>";
a.style.color = color;
panToCircle = function(latLng){
alert(latLng)
var cen = {'lat': Number(latLng[0]), 'lng': Number(latLng[1])};
map.panTo(cen);
}
// Just had to do an eval-ish thing to get it to take the current coordinates by
// formatting the string correctly, as an array. I had tried something similar
// before, but it was rejecting the arguments as a comma-separated list.
var centerString = center.lat.toString() + ',' + center.lng.toString();
a.href = 'javascript:panToCircle([' + centerString + ']);'
document.getElementById('text-div').appendChild(a);
}
Please feel free to respond with an explanation on why my closure attempt failed above. Closures are new to me (I'm new to js in general), would love to learn more. Will accept an answer that explains the closure element to the first post. Thanks!

Loop for continuous inflow information

I have 5 information coming at the same time like "long", "lat", "speed", "immat", and "date". At first, every 0.1 second new 5 of the info comes in for as long as info still available, then each time another set of the new 5 info available, another round of the same thing happen. I would like to know how to temporary keep all this values and display it.
var myPin = Table_Pins[immat];
myPin.Lat[i] = document.getElementById("Lat");
myPin.Long[i] = document.getElementById("Long");
myPin.immat[i] = document.getElementById("immat");
myPin.date[i] = document.getElementById("date");
myPin.speed[i] = document.getElementById("vitesse");
for (i=0; i<myPin.length; i++){
document.write("lat=" +myPin.Lat[i]);
document.write("long=" +myPin.Long[i]);
document.write("immat=" +myPin.immat[i]);
document.write("date=" +myPin.date[i]);
document.write("speed=" +myPin.speed[i]);
}
Use Object to store data inside an Array:
var pins = []; //this will contain a list of your objects, it's better than keeping an object containing arrays
//inside your function that stores data you will have this
var pin = {
lat: document.getElementById('Lat'),
long: document.getElementById('Long'),
immat: document.getElementById('immat'),
date: document.getElementById('date'),
speed: document.getElementById('vitesse')
};
pins.push(pin);
//and after pushing it you could add to DOM dynamically without looping over al the pins
Assuming that you have a <ul> with id="pins" you can do something similar(just after the pins.push(pin)):
var pinUl = document.getElementById('pins'); //you can declare this after pins
//and now the code that adds an element to the HTML page
var pinLi = "<li>" + JSON.stringify(pin) + "</li>";
pinUl.innerHTML += pinLi; //append the pin at the end of the other
See this example
Remember to avoid document.write!
it's just a problem of global and local variance.Avoid defined var map in function when already defined var map = null in global. Only "map"is enough without "var" in a function.

Help with Arrays and the Google Maps API

I have a google maps api function place markers which I'm using from the tutorial found here:Google Maps API with JQuery
By any means, I had to modify the javascript to account for my application. I'm pulling markers from an XML file like before, though this time I'm getting multiple requests, and multiple standard deviation, time to serve, and means for these requests. I've set up the XML to have these with a counter appended to the tag, but it looks like it's not rendering into an array correctly.
To note, I've never used Javascript, and am mostly flying by the seat of my pants on this, so if it's an atrocity of Javascript, feel free to let me know, the entire generation of the XML is in Python.
Sample of the XML: (I apologize, I don't know how to show < or > on stack overflow without it simply hiding it as a tag. Around each "markers" "marker" "name" "requestX" "timetoserveX" etc. is the < and > for tags in XML.
markers
marker
name Simpletown, CA /name
request0 /resource/ /request0
timetoserve0 .001 Seconds to serve request /timetoserve0
mean0 .5309 Mean in seconds /mean0
std_dev0 .552 Standard Deviation in Seconds /std_dev0
request1 /resource2/ /request1
timetoserve1 0.015626 Seconds to serve request /timetoserve1
mean1 0.0011 Mean in seconds /mean1
std_dev1 0.004465 Standard Deviation in Seconds /std_dev1
/marker
/markers
MYMAP.placeMarkers = function(filename) {
$.get(filename, function(xml){
$(xml).find("marker").each(function(){
var name = $(this).find('name').text();
var count = 0;
var requeststring = 'request' + Integer.toString(count)
var request = new Array();
var timetoserve = new Array();
var mean = new Array();
var std_dev = new Array();
var timetoservestring = 'timetoserve' + Integer.toString(count);
var meanstring = 'mean' + Integer.toString(count);
var std_devstring = 'std_dev' + Integer.toString(count);
while ($(this).find(requeststring).text()){
timetoservestring = 'timetoserve' + Integer.toString(count);
meanstring = 'mean' + Integer.toString(count);
std_devstring = 'std_dev' + Integer.toString(count);
request[count] = $(this).find(requeststring).text();
timetoserve[count] = $(this).find(timetoservestring).text();
mean[count] = $(this).find(meanstring).text();
std_dev[count] = $(this).find(std_devstring).text();
count++;
requeststring = 'request' + Integer.toString(count)
}
// create a new LatLng point for the marker
var lat = $(this).find('lat').text();
var lng = $(this).find('lng').text();
var point = new google.maps.LatLng(parseFloat(lat),parseFloat(lng));
// extend the bounds to include the new point
MYMAP.bounds.extend(point);
var marker = new google.maps.Marker({
position: point,
map: MYMAP.map
});
var infoWindow = new google.maps.InfoWindow();
var html = ""
for (i=0;i<count;i++){
html=html+'<strong>'+name+'</strong.><br />'+request[i]+'<br />'+timetoserve[i]+'<br />'+mean[i]+'<br />'+std_dev[i]+<br />;
//var html='<strong>'+name+'</strong.><br />'+request+'</strong.><br />'+timetoserve+'</strong.><br />'+mean+'</strong.><br />'+std_dev;
}
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(html);
infoWindow.open(MYMAP.map, marker);
});
MYMAP.map.fitBounds(MYMAP.bounds);
});
});
}
With the updates, it's not longer having the array issues it looks like, even though I am getting the map to render at this point. The "Show Markers" button is not populating the map with markers. Running FireBug with this seems to only spew endless amounts of "Break on error" hits and warnings for jQuery.
var request[count] = $(this).find(requeststring).text();
var timetoserve[count] = $(this).find(timetoservestring).text();
var mean[count] = $(this).find(meanstring).text();
var std_dev[count] = $(this).find(std_devstring).text();
to
request[count] = $(this).find(requeststring).text();
timetoserve[count] = $(this).find(timetoservestring).text();
mean[count] = $(this).find(meanstring).text();
std_dev[count] = $(this).find(std_devstring).text();
-assuming these are the arrays that are not working correctly for you.
If I may make a suggestion (RE your comment this morning):
while ($(this).find(requeststring).text()){
//...omitted
}
if you were to rename all your tags to the same thing and give them an id attribute (which is valid in xml) you can do this:
var requests = $(this).find('request');
var timetoserves = $(this).find('timetoserve');
// etc...
for (var i=0; i<requests.length; i++) {
request[i] = requests.eq(i).text();
timetoserve[count] = timetoserves.eq(i).text();
//etc...
}
which would probably provide better performance.

Saving Dragable Directions Google Directions API v3

I'm making an app with the Directions API to create biking directions. The user needs to be able to start from a custom point, add custom stopping points along the route, and actually drag the directions. Once they're done, I need to save the new route to a database.
I have the map up and running, users can add their start and waypoints via HTML input boxes, and it's perfectly dragable (sorry for the copious amounts of comments…those are primarily so I can remember what's going on…oh, and don't worry in this section about syntax…I'm copying and pasting from different parts, so I might have missed a "}"…all of this code functions):
function initialize() {
var rendererOptions = {
draggable: true,
//end redererOptions
};
directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
var chicago = new google.maps.LatLng(42.73352,-84.48383);
var myOptions = {
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: chicago,
//end myOptions
}
//create the world of the dream (define where the map's gonna go
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
//and the subject fills it with it's subconscious (Call the map from Directions and place it in the div we've created)
directionsDisplay.setMap(map);
//tell google where the printed directions will go
directionsDisplay.setPanel(document.getElementById("directions_detail"));
//end initialize()
};
function calcRoute() {
//get start string from Start input box
var start = document.getElementById("start").value;
//get end string from End input box
var end = document.getElementById("end").value;
//set up an array for waypoints
var waypts = [];
//define where the waypoints will come from
var checkboxArray = document.getElementById("waypoints");
//loop to retrieve any waypoints from that box
for (var i = 0; i < checkboxArray.length; i++) {
//if any options in the select box are selected, add them
if (checkboxArray.options[i].selected == true) {
waypts.push({
//set up parameters to push to the Directions API
location:checkboxArray[i].value,
//make them explicit waypoints that separate the route into legs
stopover:true});
//end if loop
}
//call the Directions Service API, pass the request variable (above) and call a function asking for the response and status objects
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK)
{
//pass the response object to the map
directionsDisplay.setDirections(response);
//set up a route variable for the upcoming loop
var route = response.routes[0];
//set up a variable for the route summary, define the <div> where this will be presented to the user
var summaryPanel = document.getElementById("directions_panel");
//clear the <div>
summaryPanel.innerHTML = "";
//turn direcitons_panel "on"...gives the div a color (in my css)
summaryPanel.className = "directions_panel_on";
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
//set up a route segment variable to display a 1-based segment for the segment summary in html
var routeSegment = i + 1;
summaryPanel.innerHTML += "<b>Route Segment: " + routeSegment + "</b><br />";
summaryPanel.innerHTML += route.legs[i].start_address + " to ";
summaryPanel.innerHTML += route.legs[i].end_address + "<br />";
summaryPanel.innerHTML += route.legs[i].distance.text + "<br /><br />";
//end for loop
};
//end directionsService() function
});
//end calcRoute() function
}
SO. You have my base. Everything's functional (I have it running on my server)…users can create a fully customized map. I just can't save it if they decided to drag the path of the route because I need to call an object with AJAX, and the only object I know how to call is tied to the request variable, which defines the waypoints array as the hard stopover points that split the route up.
I know that the array I'm looking for is called via_waypoint[]inside of the legs[] array…it's just getting an object out of the damn DirectionsRenderer with the stupid via_waypoints[] array populated. I have the rest ready to go on the PHP/MySqul and AJAX side of things.
I've already tried this tutorial…and I can't get it to work. The main answer itself says it left out a lot, and I'm so new to JavaScript, it appears (s)he left out too much for me.
I was trying for saving waypoints and this should be useful to other users who are searching for the same.
I have created set of scripts to save the directions waypoints in the database also code to fetch that information back and display the waypoints in another map. I have provided html, php and sql files and complete explanation in this link.
http://vikku.info/programming/google-maps-v3/draggable-directions/saving-draggable-directions-saving-waypoints-google-directions-google-maps-v3.htm.
Also you can copy the source code of that page and you can edit according to your preference and use the sql file for the database.
UPDATE: I've found my own answer.
The object that houses the most current map data is directionsDisplay.directions. That object holds the data from the map AS SHOWN…including the via_waypoints[] array, which shows up as a child of the legs[] array.
The code below shows how you can print the string for your analyzing pleasure (I've made this function to be called by a button on the HTML side):
//GET THE JSON Object
var newString = JSON.stringify(directionsDisplay.directions);
//set up area to place drop directionsResponse object string
var directions_response_panel = document.getElementById("directions_response");
//dump any contents in directions_response_panel
directions_response_panel.innerHTML = "";
//add JSON string to it
directions_response_panel.innerHTML = "<pre>" + newString + "</pre>";
Lesson of the night: directionsDisplay.directions calls on the map data AFTER a user has made dragable changes to their directions.

Categories