The Senario:
I have a google map and one of its functions is to show routing information from a central point to several diffrent locations on the same map.
I know that each direction object can only show one set of routes at time so I have a function that create a render object each time it is called and places the route on the map and call it for each location.
The code:
the function to calculate and display the route:
function calculateRoot (startLocation,wayPoints,endLocation) {
var selectedMode = $("#travelMode").val();
// create a new directions service object to handle directions requests
var directionsService = new google.maps.DirectionsService();
// create a directions display to display route info on the map
var directionsDisplay = new google.maps.DirectionsRenderer();
directionsDisplay.setMap(map);
// Stops the default googlemarkers from showing
directionsDisplay.suppressMarkers = true;
directionsDisplay.setPanel(document.getElementById("directions"));
// create a request object
var request = {
origin:startLocation,
waypoints: wayPoints,
destination:endLocation,
travelMode: google.maps.TravelMode[selectedMode],
optimizeWaypoints:true,
provideRouteAlternatives:true,
transitOptions: {
departureTime: new Date()
}
};
directionsService.route(request, function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
return directionsDisplay;
}
else if (status == google.maps.DirectionsStatus.ZERO_RESULTS){
alert ('No routing can be found for this Journey');
return -1;
}
else {
alert ('The following error occoured while attempting to obtain directions information:'+'\n'+status + '\n' + 'For:'+ ' '+ request.destination);
return -1;
}
});
}
The all locations function:
function showAllRoutes(){
if ( ! directionsArray.length < 1) {
// if directions are already displayed clear the route info
clearRoots();
}
$('#directions').empty();
// craete an empty waypoint array just to pass to the function
var wayPoints = [];
for (var i = 0; i< markerArray.length; i++) {
var directions = calculateRoot(startLatLng,wayPoints,markerArray[i].position);
directionsArray.push(directions);
}
sizeMap();
$('#directions').show();
}
The function to clear the route(s)
function clearRoutes() {
if (directionsArray.length <1 ) {
alert ("No directions have been set to clear");
return;
}
else {
$('#directions').hide();
for (var i = 0;i< directionsArray.length; i ++) {
if (directionsArray [i] !== -1) {
directionsArray [i].setMap(null);
}
}
directionsArray.clear();
map.setZoom(5);
return;
}
}
The problem:
While generating and displaying the routes seems to work fine no matter what I do I cant clear the routes from the map unless I refresh.
Am I missing something simple or can what I need to do not be done in this way, I've been trying to get this working for more than a day now and I'm stumped.
Can anyone help?
Thanks in advance
The directions service is asynchronous, calculateRoot (or calculateRoute or whatever it is really called) can't return anything, you need to push the routes into a global array in the callback function.
This is your callback function:
function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
return directionsDisplay;
}
change it to something like:
function(result,status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
directionsArray.push(directionsDisplay);
}
Related
Working with this example from Bing Maps V8
This line kicks off the map search: geocodeQuery("New York, NY");
This code fragment contains the callback function (which places the pin on the map):
var searchRequest = {
where: query,
callback: function (r) {
//Add the first result to the map and zoom into it.
if (r && r.results && r.results.length > 0) {
var pin = new Microsoft.Maps.Pushpin(r.results[0].location);
map.entities.push(pin);
map.setView({ bounds: r.results[0].bestView });
}
},
errorCallback: function (e) {
//If there is an error, alert the user about it.
alert("No results found.");
}
};
If I want to execute multiple searches, I can write:
geocodeQuery("New York, NY");
geocodeQuery("Queens, NY");
Now I have 2 pins on the map.
Next step: if I want to label the pins I'd extend the 'new pushpin code':
var pin = new Microsoft.Maps.Pushpin(r.results[0].location,
{
title: 'Queens',
text: '2'
});
Question:
The pins are placed by the callback function. Since I want each pin to have different text, how do I tweak this sample code so that I can pass new parameters to the callback function?
You would need to add an argument to the geocodeQuery function (maybe called title) and use that when you create the pin in the callback. Then you could simply call geocodeQuery("Queens, NY", "Queens")
function geocodeQuery(query, title) {
//If search manager is not defined, load the search module.
if (!searchManager) {
//Create an instance of the search manager and call the geocodeQuery function again.
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
searchManager = new Microsoft.Maps.Search.SearchManager(map);
geocodeQuery(query, title);
});
} else {
var searchRequest = {
where: query,
callback: function (r) {
//Add the first result to the map and zoom into it.
if (r && r.results && r.results.length > 0) {
var pin = new Microsoft.Maps.Pushpin(r.results[0].location, {
title: title,
text: '2'
});
map.entities.push(pin);
map.setView({ bounds: r.results[0].bestView });
}
},
errorCallback: function (e) {
//If there is an error, alert the user about it.
alert("No results found.");
}
};
//Make the geocode request.
searchManager.geocode(searchRequest);
}
}
I am using renderDirections and requestDirections methods to create multiple routes called in a loop, now i want to clear them before calling that loop because even when there is no route data it shows that of past data.
PS: The methods are not called when no data is there but it shows previous routes
Below is my sample code to set routes:
function renderDirections(result, Driver) {
var directionsRenderer = new google.maps.DirectionsRenderer({ suppressMarkers: false,preserveViewport: true, polylineOptions: { strokeColor: colors[cur] } });
directionsRenderer.setMap(map);
directionsRenderer.setDirections(result);
var leg = result.routes[0].legs[0];
makeMarker(leg.start_location, Driver);
makeMarker(leg.end_location, Driver);
cur++;
}
function requestDirections(start, end, wps, Driver) {
var directionsService = new google.maps.DirectionsService;
directionsService.route(
{
origin: start,
destination: end,
waypoints: wps,
travelMode: google.maps.DirectionsTravelMode.DRIVING
}, function (result) {
renderDirections(result, Driver);
});
}
You can simply put your routes in an array, when you initialize it, like this: routes.push (myRoute) and next use the method route.setMap () with the argument null to disappear them of the map. You can also remove them if you reset the array like this: routes = []; or routes[2]=null for one element. I give you some methods to remove or just disappear all routes.
// Sets the map on all routes in the array.
function setMapOnAll(map) {
for (var i = 0; i < markers.length; i++) {
routes[i].setMap(map);
}
}
// Removes the routes from the map, but keeps them in the array.
function clearMarkers() {
setMapOnAll(null);
}
// Shows any routes currently in the array.
function showMarkers() {
setMapOnAll(map);
}
// Deletes all routes in the array by removing references to them.
function clearAllMarkers() {
clearMarkers();
routes = [];
}
This worked for me: declaring directionsRender[] globally and setting up a counter to loop on old routes to clear it `
function renderDirections(result, Driver) {
directionsRenderer[cnt] = new google.maps.DirectionsRenderer();
directionsRenderer[cnt].setMap(map);
directionsRenderer[cnt].setDirections(result);
cnt++;
}`
function clearRoutes() {
for (var i = 0; i < cnt; i++) {
directionsRenderer[i].setMap(null);
}
}
In my app I am trying to get a Region (city) to store in my Location model along with lngLat and address.
The thing is that for new locations it would be easy as when I would create I would do that. For old locations I wrote this bit of code
function geocodeLatLng(geocoder, latlngStr, callback) {
var latlng = { lat: parseFloat(latlngStr.split(',')[0]), lng: parseFloat(latlngStr.split(',')[1]) };
var city;
geocoder.geocode({ 'location': latlng }, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
var address = results[0].address_components;
for (var p = address.length - 1; p >= 0; p--) {
if (address[p].types.indexOf("locality") != -1) {
console.log(address[p].long_name);
city = address[p].long_name;
callback(city);
}
}
}
});
}
And I am calling it like this
self.getRegion = function () {
for (var i = 0; i < self.rawLocations().length; i++) {
var location = self.rawLocations()[i];
setTimeout(
geocodeLatLng(geocoder, location.systemRepresentation(), function (res) {
}), 250);// End of setTimeOut Function - 250 being a quarter of a second.
}
}
The issue is that I get over_query_limit after 5 calls. I will store the Location it self in the database for now I would have to do this to fix the old locations.
Any headers?
Google maps javascript library has a maximum calls per second as well as an hourly rate, are you trying to geocode at a rate faster than their per second rate possibly?
5 does seem low as their own documents inform users that it is 50 per second (https://developers.google.com/maps/documentation/geocoding/usage-limits)
Also have you signed up for a key and are using it? This could make a difference (if the google account is old as signed up for maps API sometime you can use the system without a key)
Objective
I wish to remove the duplication code brought by the anonymous function calls.
Background
I am doing a very simple project where I use Google Maps API to show a map with two searchboxes. The user puts a start address and an end address in those boxes and I show markers in the map.
To achieve this I have two anonymous functions for the listeners, which are exactly equal except for one point - one uses the startSearchBox and the other one the endSearchBox.
What I tried
This duplication of code is unnecessary, and so I tried to pass the searchboxes as a parameter to the anonymous function, however that didn't work.
I also considered creating the searchboxes as global variables, but that is a bad practice I wish to avoid.
How can I eliminate the duplication in this code?
Code
function initSearchBoxes() {
// Create the search box and link it to the UI element.
let startInput = document.getElementById('start-input');
let startSearchBox = new google.maps.places.SearchBox(startInput);
let endInput = document.getElementById('end-input');
let endSearchBox = new google.maps.places.SearchBox(endInput);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
startSearchBox.setBounds(map.getBounds());
endSearchBox.setBounds(map.getBounds());
});
startSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = startSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
endSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = endSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
}
You can wrap your callback function in another "factory" function. The factory will take a parameter (the search box reference) and then it will return the actual handler:
function makeSearchHandler(searchBox) {
return function() {
deleteAllMarkers();
let places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
};
}
That function contains the code from your original, but instead of directly referring to either startSearchBox or endSearchBox, it uses the parameter passed to the factory. The returned function will therefore work like yours, but the code is only present once.
You can then use that function to create the callbacks:
startSearchBox.addListener('places_changed', makeSearchHandler(startSearchBox));
endSearchBox.addListener('places_changed', makeSearchHandler(endSearchBox));
I'm trying to sort through an array of distances generated by google maps. I need to order my list, closest to furthest. I can get all of the directions and distances displayed just fine with the directionsService api example, but I cannot figure out how to retrieve that info outside of the function so that I can sort it.
function calcDistances() {
for (var x = 0; x < wineries.length; x++) {
var winery = wineries[x];
var trdistances = [];
var request = {
origin: map.getCenter(),
destination: new google.maps.LatLng(winery[1], winery[2]),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
var summaryPanel = document.getElementById("tasting_rooms_panel");
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
//this works fine and displays properly
summaryPanel.innerHTML += route.legs[i].distance.text;
//I want to store to this array so that I can sort
trdistances.push(route.legs[i].distance.text);
}
}
});
alert(trdistances[0]);//debug
}
}
As commented in the code, I can populate summaryPanel.innerHTML, but when I populate the array trdistances, the alert gives me "undefined". Is this some rookie javascript coding error? I read up on the scope of variables and this should work. Help me oh wise ones.
function calcDistances() {
for (var x = 0; x < wineries.length; x++) {
var winery = wineries[x];
var trdistances = [];
var request = {
origin: map.getCenter(),
destination: new google.maps.LatLng(winery[1], winery[2]),
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
//Using Closure to get the right X and store it in index
(function(index){
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
var summaryPanel = document.getElementById("tasting_rooms_panel");
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
//this works fine and displays properly
summaryPanel.innerHTML += route.legs[i].distance.text;
//I want to store to this array so that I can sort
trdistances.push(route.legs[i].distance.text);
}
if(index == wineries.length-1){ //check to see if this is the last x callback
console.log(trdistances); //this should print the result
//or in your case you can create global function that gets called here like sortMahDistance(trdistances); where the function does what you want.
printMyDistances(trdistances); //calls global function and prints out content of trdistances console.log();
}
}
});
})(x); //pass x into closure as index
}
}
//on global scope
function printMyDistances(myArray){
console.log(myArray);
}
The problem is scope to keep track of the for loop's X. Basically, you have to make sure all the callbacks are done before you can get the final result of trdistances. So, you'll have to use closure to achieve this. By storing first for loops' X into index within closure by passing X in as index, you can check to see if the callback is the last one, and if it is then your trdistances should be your final result. This mod of your code should work, but if not please leave comment.
Furthermore, I marked up my own version of google map using closure to resolve using async directionServices within for loop, and it worked. Here is my demo jsfiddle. Trace the output of console.log(); to see X vs index within the closure.