jQuery sort function not working properly - javascript

Based on the user location and a store location I try to figure out what the distance is between this two. This is working, but what I want is an array with all the values and sort on the distance between the two points.
I have my add_stores_to_array function, wich add all stores to the array stores when it is looping through the JSON file.
add_stores_to_array = function(position) {
var user_latitude = position.coords.latitude;
var user_longitude = position.coords.longitude;
$.getJSON('/stores').done(function(data) {
$.each(data.features, function(i, item) {
var store_latitude = item.geometry.coordinates[1];
var store_longitude = item.geometry.coordinates[0];
var user = new google.maps.LatLng(user_latitude, user_longitude);
var store = new google.maps.LatLng(store_latitude, store_longitude);
var directionsService = new google.maps.DirectionsService();
var request = {
origin:user,
destination:store,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var response = Math.ceil(response.routes[0].legs[0].distance.value / 1000);
// add distance and store id to the array stores
stores.push({distance: response, id: item.properties.Nid});
}
});
});
// call the sort function
sort_stores(stores);
console.log(stores);
});
};
After the $.each I call the sort function. But after logging it to the console, it is still not sorted.
My sort_stores function:
sort_stores = function(stores){
stores.sort(function(a, b){
return a.distance - b.distance;
});
};
First I thought it wasn't working because the $.each was still running, but after adding this code, it still doesn't working:
if (i == Object.keys(data.features).pop()) {
sort_stores(stores);
}
So, I tried something different. I call the sort_stores(stores) function in the $.each.
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var response = Math.ceil(response.routes[0].legs[0].distance.value / 1000);
stores.push({distance: response, id: item.properties.Nid});
sort_stores(stores);
}
});
And it works.. the array is sorted based on the value distance in the array. But now he sorts the array after each added store.. not really effective.
Is there a proper way to call the sort_stores(stores) function one time, and sort it when all stores are added to the array?
EDIT:
If I place an alert() before the sort_stores(stores) it is working..
if (status == google.maps.DirectionsStatus.OK) {
var response = Math.ceil(response.routes[0].legs[0].distance.value / 1000);
stores.push({distance: response, id: item.properties.Nid});
}
});
});
alert('Call the sort_stores(stores) function after the $.each, with an alert.. it is working?');
sort_stores(stores);
});
};
Edit 2:
Normally I call the function add_stores_to_array from here?
get_user_location = function(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(add_stores_to_array);
}
};

There's nothing wrong with your sort function. The problem is that directionsService.route is asynchronous call and the rest of the code will run even when all the calls are not completed yet.
You can use jQuery.when(). Here is the new add_stores_to_array() function
add_stores_to_array = function(position) {
var promises = []; //ADDED promise array
var user_latitude = position.coords.latitude;
var user_longitude = position.coords.longitude;
$.getJSON('/stores').done(function(data) {
$.each(data.features, function(i, item) {
var store_latitude = item.geometry.coordinates[1];
var store_longitude = item.geometry.coordinates[0];
var user = new google.maps.LatLng(user_latitude, user_longitude);
var store = new google.maps.LatLng(store_latitude, store_longitude);
var directionsService = new google.maps.DirectionsService();
var request = {
origin:user,
destination:store,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
var dfd = directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var response = Math.ceil(response.routes[0].legs[0].distance.value / 1000);
// add distance and store id to the array stores
stores.push({distance: response, id: item.properties.Nid});
}
});
promises.push(dfd); //ADDED store each object in array
});
//Now you can do the following without having any async issue.
$.when.apply(null, promises).done(function() {
/* sort & do stuff here */
sort_stores(stores);
console.log(stores);
});
});
};
EDIT
Here is another approach. Since you need to wait until all responses are returned, you can customize your sort function to check for the response count. If it's equal to total (which means all calls have finished successfully) then sort the array.
sort_stores = function(stores, responseCount, totalCount ) {
if (responseCount == totalCount) {
stores.sort(function(a, b){
return a.distance - b.distance;
});
}
};
Then change the add_stores_to_array function as follows.
add_stores_to_array = function(position) {
var user_latitude = position.coords.latitude;
var user_longitude = position.coords.longitude;
$.getJSON('/stores').done(function(data) {
var totalCount = data.features.length; //ADDED Get total count
var responseCount = 0; //ADDED
$.each(data.features, function(i, item) {
var store_latitude = item.geometry.coordinates[1];
var store_longitude = item.geometry.coordinates[0];
var user = new google.maps.LatLng(user_latitude, user_longitude);
var store = new google.maps.LatLng(store_latitude, store_longitude);
var directionsService = new google.maps.DirectionsService();
var request = {
origin:user,
destination:store,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var response = Math.ceil(response.routes[0].legs[0].distance.value / 1000);
// add distance and store id to the array stores
stores.push({distance: response, id: item.properties.Nid});
responseCount++; //ADDED
sort_stores(stores, responseCount, totalCount); //ADDED Call sort function here
}
});
});
});
};

Related

Google map marker with angularjs for large data

I am building an widget where user can upload an excel file and the places are get marked in the google map.
The following code works, but issue comes when i am reading an large excel file with 10k amount of data, the browser gets stuck. I am using a for loop and adding some timeout to get the data from the google api.
I pass the city name and get the latitude and longitude and mark it on the map.
Is there a better way i can implement?
Here is the code:
function googleMapsInit(widId, $scope, $http, $rootScope, $mdDialog) {
$scope.finish = function() {
var objIndex = getRootObjectById(widId, $rootScope.dashboard.widgets);
$mdDialog.hide();
document.getElementById('map').innerHTML = "";
//excel data
var array = JSON.parse($rootScope.json_string);
$scope.locationData = [];
//dividing it to chunks
var k,j,temparray,chunk = 8;
for (k=0,j=array.length; k<j; k+=chunk) {
temparray = array.slice(k,k+chunk);
var i;
//getting the longitude and latitude from the google geo api
for(i=0;i < temparray.length ; i++){
Geocode(temparray[i].PLACE_OF_ACCIDENT);
}
}
//sometimes data gets delayed
setTimeout(function(){ googleMap(); }, 5000);
};
function Geocode(address) {
var obj = {};
var geocoder = new google.maps.Geocoder();
geocoder.geocode({'address': address}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
obj = {
lat : results[0].geometry.location.G,
lng : results[0].geometry.location.K
};
setTimeout(function(){ $scope.locationData.push(obj); }, 100);
}
else if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
setTimeout(function() {
Geocode(address);
}, 100);
}
else if (status === google.maps.GeocoderStatus.ZERO_LIMIT) {
setTimeout(function() {
Geocode(address);
}, 100);
}
else {
}
});
}
function googleMap() {
var dataStore = $scope.locationData;
var array = JSON.parse($rootScope.json_string);
var map = new google.maps.Map(document.getElementById('map'),{
center: {lat: 7.85, lng: 80.65},
zoom: 6 });
var pinImageGreen = new google.maps.MarkerImage("http://maps.google.com/mapfiles/ms/icons/green-dot.png");
var pinImageBlue = new google.maps.MarkerImage("http://maps.google.com/mapfiles/ms/icons/blue-dot.png");
var marker = [];
var k;
for(k=0; k < array.length; k++){
marker[k] = new google.maps.Marker({
position: {lat: $scope.locationData[k].lat, lng: $scope.locationData[k].lng},
map: map,
title: array[k].PLACE_OF_ACCIDENT,
icon: pinImageGreen,
VEHICLE_TYPE: array[k].VEHICLE_TYPE,
VEHICLE_USAGE: array[k].VEHICLE_USAGE,
VEHICLE_CLASS: array[k].VEHICLE_CLASS
});
marker[k].addListener('click', function(data) {
var j;
for(j=0;j<array.length;j++){
if(($scope.locationData[j].lat == data.latLng.G) && ($scope.locationData[j].lng == data.latLng.K )){
document.getElementById("details").innerHTML =
array[j].PLACE_OF_ACCIDENT + "</br>" +
array[j].VEHICLE_TYPE + "</br>" +
array[j].VEHICLE_USAGE + "</br>" +
array[j].VEHICLE_CLASS + "</br>" ;
}
}
});
}
}
$scope.cancel = function() {
$mdDialog.hide();
};
}
One way to slightly improve performance is this: Instead of adding markers to the map one at a time, just create the markers array separately and then add them all at once to the map. Here is a sample code:
var markersData = [];
for (var i = 0; i < myArray.length; i++) {
var item = scope.myArray[i];
if (item.lat != undefined && item.lon != undefined) {
var icon = 'icon.png';
markersData.push({
lat: item.lat,
lng: item.lon,
title: 'xyz'
});
}
}
map.addMarkers(markersData);
By the way I have used "gmaps.js" for this which simplifies coding google maps, but you don't necessarily need it. The general idea is to avoid adding markers to the map inside the loop, one by one.

Will there be any inconsistency with the value calculated?

I have a javascript code like below to calculate the total distance between n markers.
var distance = 0;
function updateTimeAndDistance(timeAndPath) {
realtracPath = timeAndPath.path;
getDistance();
console.log("calculated distance : " + distance);
}
function getDistance() {
for ( var i = 0; i < realtracPath.length - 1 ; i++) {
var startPos = new google.maps.LatLng(realtracPath[i].lat, realtracPath[i].lng);
var endPos = new google.maps.LatLng(realtracPath[i+1].lat, realtracPath[i+1].lng);
var request = {
origin : startPos,
destination : endPos,
travelMode : google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
distance += response.routes[0].legs[0].distance.value;
}
});
}
}
but I am worried that, will there be any inconsistency in the value of the distance calculated as the distance is calculated asynchronously.
EDIT: every time I run this, I am getting distance as zero. I am not understanding why, though I have used the global distance variable.
Thanks.
Try introducing an async callback function like so;
var distance = 0;
function updateTimeAndDistance(timeAndPath) {
realtracPath = timeAndPath.path;
getDistance(function(){
console.log("calculated distance : " + distance);
});
}
function getDistance(cb) {
var latch = realtrackPath.length;
for ( var i = 0; i < realtracPath.length - 1 ; i++) {
var startPos = new google.maps.LatLng(realtracPath[i].lat, realtracPath[i].lng);
var endPos = new google.maps.LatLng(realtracPath[i+1].lat, realtracPath[i+1].lng);
var request = {
origin : startPos,
destination : endPos,
travelMode : google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
distance += response.routes[0].legs[0].distance.value;
latch--
if (latch==0){
cb()
}
}
});
}
}
Note: If the status does not come back as OK nothing will happen. It can be quite handy to have callbacks with function signatures that pass booleans indicating errors etc.

Storing directionsService.route response in object array, and display in another method

I have various origins/destinations, and am looping through all of them to calculate each distance. Pushing each to an array of the following object
routes {
distance: xx,
response:response
}
After all the routes have been added I calculate which has the smallest distance, and map that response. However I am outside the directionService now.. is there a way to map stored responses without calling directionService?
Thanks!!
var routes = [];
function route() {
var request = {
origin:start,
destination:end,
travelMode: google.maps.TravelMode.DRIVING
};
calcRoute(request);
//Here I'd like to display, say routes[0].response.
}
function calcRoute() {
var start = document.getElementById("start").value;
var end = document.getElementById("end").value;
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
// For each route, display summary information.
var distance = 0;
for (var i = 0; i < route.legs.length; i++) {
distance = distance + route.legs[i].distance.text;
};
//push to routes
routes.push({response:response,distance:distance});
}
});
}

not able to access global variable while using google maps api js

In the below code, I am not able to access the value of variable distances . I think that is because of asynchronous call directionsService.route. How can I get the value variable distances ?
var totalDistance;
var distances = new Array();
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
var start = "ABC XYZ";
var end ;
var points = new Array("Location ABC", "Location PQR", "Location XYZ", "Location more", "And Some other location");
function initialize() {
directionsDisplay = new google.maps.DirectionsRenderer();
var mapOptions = {
center: new google.maps.LatLng(13.0604220, 80.2495830),
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROADMAP,
draggableCursor: "crosshair"
};
map = new google.maps.Map(document.getElementById("map-canvas"),
mapOptions);
directionsDisplay.setMap(map);
}
function calcRoute() {
for(var j=0;j<points.length;j++)
{
end = points[j];
var waypoints = new Array();
for(var i=0; i<points.length;i++)
{
if(i!=j)
{
waypoints.push({location:points[i], stopover: true});
}
}
var request = {
origin: start,
destination: end,
waypoints: waypoints,
optimizeWaypoints: true,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
totalDistance = 0;
for ( var i=0;i<route.legs.length;i++)
{
totalDistance+=route.legs[i].distance.value;
}
distances.push(totalDistance);
}
});
}
/*Now I want my distances value to be accessed from here i.e outside for loop.*/
/*So that I can compare all the distances obtained */
}
google.maps.event.addDomListener(window, 'load', initialize);
Edit: I have updated complete code.
What I am trying to do : I have fixed start point and some waypoints (order not fixed), I am trying to optimize waypoints, my end point is not fixed, it can be any so that to optimize the path, but it is necessary to provide end point while calling directionsService.route method , so I am taking one of the waypoints as end point and keeping rest other in waypoints only and then calculating total distance of the route. So each of the waypoint will become end point one by one , and others will remain waypoint. I will calculate total distance of all the combinations and then I will show only the directions of the route which has minimum distance.
I would avoid calling asynchronous functions from inside a loop. It can be a pain keeping track of everything.
From what I understand of the question you are trying to find the shortest route with an arbitrary number of destinations. Instead of looping through each waypoint, pass the starting address and all of the destinations to the DistanceMatrix service which returns all of the route lengths from the origin to each waypoint. When the results return sort from shortest to longest. The longest destination will be the end address. Then pass the start address, end address, and remaining waypoints to the DirectionService with the optimizeWaypoints turned on.
Demo: http://jsfiddle.net/bryan_weaver/snYJ2/
Relavent Code:
var map;
var origin = "4100 Ashby Road, St. Ann, MO 63074"
var destinations = [
"2033 Dorsett Village, Maryland Heights, MO 63043",
"1208 Tamm Avenue, St. Louis, MO 63139",
"1964 S Old Highway 94 St Charles, MO 63303"];
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
function calculateDistances() {
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix({
origins: [origin], //array of origins
destinations: destinations, //array of destinations
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
}, callback);
}
function callback(response, status) {
if (status != google.maps.DistanceMatrixStatus.OK) {
alert('Error was: ' + status);
} else {
//we only have one origin so there should only be one row
var routes = response.rows[0];
var sortable = [];
var resultText = "Origin: <b>" + origin + "</b><br/>";
resultText += "Possible Routes: <br/>";
for (var i = routes.elements.length - 1; i >= 0; i--) {
var rteLength = routes.elements[i].duration.value;
resultText += "Route: <b>" + destinations[i] + "</b>, "
+ "Route Length: <b>" + rteLength + "</b><br/>";
sortable.push([destinations[i], rteLength]);
}
//sort the result lengths from shortest to longest.
sortable.sort(function (a, b) {
return a[1] - b[1];
});
//build the waypoints.
var waypoints = [];
for (j = 0; j < sortable.length - 1; j++) {
console.log(sortable[j][0]);
waypoints.push({
location: sortable[j][0],
stopover: true
});
}
//start address == origin
var start = origin;
//end address is the furthest desitnation from the origin.
var end = sortable[sortable.length - 1][0];
//calculate the route with the waypoints
calculateRoute(start, end, waypoints);
//log the routes and duration.
$('#results').html(resultText);
}
}
//Calculate the route of the shortest distance we found.
function calculateRoute(start, end, waypoints) {
var request = {
origin: start,
destination: end,
waypoints: waypoints,
optimizeWaypoints: true,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function (result, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(result);
}
});
}
function initialize() {
directionsDisplay = new google.maps.DirectionsRenderer();
var centerPosition = new google.maps.LatLng(38.713107, -90.42984);
var options = {
zoom: 12,
center: centerPosition,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map($('#map')[0], options);
geocoder = new google.maps.Geocoder();
directionsDisplay.setMap(map);
calculateDistances();
}
google.maps.event.addDomListener(window, 'load', initialize);
The request is asynchronous.
You have got to wait until the request completes before you check your global variable.
See this answer
Is there any way to wait until the DirectionsService returns results?
EDIT
If you really in a fix you can try making a Synchronous call with
jQuery.ajaxSetup({async:false});
making sure you turn it on again after the method completes
jQuery.ajaxSetup({async:true});
This comes with huge warning however as it can cause your browser to lock up use with
caution
Your alert is firing before the callback has been fired. I'd suggest you create another function and call that from your directionsService.route success callback e.g
var totalDistance; /*Global Variable */
var distances = new Array();
var directionsService = new google.maps.DirectionsService();
/* Some request */
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
var route = response.routes[0];
totalDistance = 0;
for ( var i=0;i<route.legs.length;i++)
{
totalDistance+=route.legs[i].distance.value;
}
distances.push(totalDistance);
}
afterComplete();// New function call moved outside for loop
});
function afterComplete(){
alert(distances); //Will display null
}
You could then also remove the global variable and actually pass it into the afterComplete function, thus eliminating the need for a global (unless of course it is needed elsewhere)
The DirectionsService is asynchronous. You need to use the results inside the call back function:
function calcRoute() {
for(var j=0;j<points.length;j++)
{
end = points[j];
var waypoints = new Array();
for(var i=0; i<points.length;i++)
{
if(i!=j)
{
waypoints.push({location:points[i], stopover: true});
}
}
var request = {
origin: start,
destination: end,
waypoints: waypoints,
optimizeWaypoints: true,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var route = response.routes[0];
totalDistance = 0;
for ( var i=0;i<route.legs.length;i++)
{
totalDistance+=route.legs[i].distance.value;
}
distances.push(totalDistance);
}
else { alert("Distance Service request not successful:"+status); }
if (distances.length == points.length){
alert(distances);
}
});
}
The global variable will be available after the data comes back from the server and the callback function finishes running. If you need to do something with it once it is available, do that in the call back function.
example

push to array within function

I'm trying to build an array of latitudes and longitudes for start/end points for routing.
function parkRoute(){
var start = $('#start').val();
var end = $('#end').val();
var points = [];
function printPoints(points){
console.log(points)
}
function getXY(add){
geocoder.geocode( {'address': add + ", glendale, ca"}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0].geometry.location) {
var lon = results[0].geometry.location.kb;
var lat = results[0].geometry.location.jb;
points.push(lat,lon);
console.log(points)
}
}
})
}
getXY(start);
getXY(end);
printPoints(points);
}
It prints out an empty array first even though I'm calling another function to print them after the function to create the array.
[] parks1.js:192
[34.1480811, -118.24759369999998] parks1.js:201
[34.1480811, -118.24759369999998, 34.1851925, -118.27651679999997] parks1.js:201
What am I doing wrong?
You are printing the array contents before pushing. The geocoding operation is asynchronous, so you have to print from the async callback.
The problem is that you're not waiting for all the callbacks, which are asynchronous, to be executed.
You could do this :
var inProcess = 0; // number of tasks we're waiting for
function getXY(add){
inProcess++; // increment the number of tasks
geocoder.geocode( {'address': add + ", glendale, ca"}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0].geometry.location) {
var lon = results[0].geometry.location.kb;
var lat = results[0].geometry.location.jb;
points.push(lat,lon);
console.log(points)
}
}
oneProcessIsDone();
})
}
function oneProcessIsDone() {
if (--inProcess==0) { // decrement the number of tasks, and print the points if all are done
printPoints(points);
}
}
getXY(start);
getXY(end);

Categories