How can I get the distance and time from L.Routing.control? - javascript

I am drawing a route on a leaflet map, it works good and in the control it shows the distance and estimated time of arrival. Is there a way to extract both of them and save them?
The code for the L.Routing.control
function getroute() {
myroutewithout = L.Routing.control({
waypoints: [
L.latLng(window.my_lat, window.my_lng),
L.latLng(window.job_p_lat, window.job_p_lng)
],show: true, units: 'imperial',
router: L.Routing.mapbox('API-KEY-HERE'),
createMarker: function(i, wp, nWps) {
if (i === 0 || i === nWps + 1) {
// here change the starting and ending icons
return mymarker = L.marker(wp.latLng, {
icon: operatoricon
});
} else {
return job_start = L.marker(wp.latLng, {
icon: jobicon
});
}
}
}).addTo(map);

You can achieve that using the code from this issue
var routeControl = L.Routing.control({...});
...
routeControl.on('routesfound', function(e) {
var routes = e.routes;
var summary = routes[0].summary;
// alert distance and time in km and minutes
alert('Total distance is ' + summary.totalDistance / 1000 + ' km and total time is ' + Math.round(summary.totalTime % 3600 / 60) + ' minutes');
});
Demo

Related

Geojson AJAX calls interfere with onEachFeature functions

The purpose of the map is when a user selects a HTML input it pulls a geojson file and places the zip code information on the map for that state. Then a second geojson file will pull the state bounds and also put it on the map and zoom to that state. Currently I am trying to accomplish this with two Geojson AJAX calls. One is called geojson and the other is statelay (see below). When I just have the map run with only the geojson object it runs fine and the interaction with the zip codes runs smoothly. However, when I add statelay the interactivity no longer works with the first geosjon call. Here is what the interactivity looks like when it works: https://www.freepropertycalc.com/maps.
Here are the specific questions:
-Why does adding the statelay object below interfere with the interactivity of the other geojson call?
-How can I simply get the bounds from statelay and make the map zoom to those state bounds?
Code:
<select id="select" >
<option value="">State </option>
<option value="{{url_for('static', filename= 'AK.js' )}}">AK</option>
<option value="{{url_for('static', filename= 'AL.js' )}}">AL</option>
<!-- a whole bunch more states -->
</select>
</div>
<div id="map2" class = "content-section middle toppad" ></div>
<script type="text/javascript">
var state = '/static/state.js';
var select = document.getElementById('select') ;
select.addEventListener('change', function(){
var ourRequest = new XMLHttpRequest();
var strUser = select.options[select.selectedIndex].text;
ourRequest.open('GET',select.value)
ourRequest.onload = function(){
document.getElementById('map2').innerHTML = "<div id='llmap'></div>";
var TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
var MB_ATTR = 'Map data © OpenStreetMap contributors';
map = L.map('llmap', {});
L.tileLayer(TILE_URL, {attribution: MB_ATTR}).addTo(map);
map.setView(new L.LatLng(38.500000, -98.000000), 4);
function onLocationError(e) {
alert(e.message);
}
// control that shows state info on hover
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h6>Market Info by ZIP Code</h6>' + (props ?
'<b>' + 'ZIP: ' + props.ZIP + '</b><br />' +
'Median Home Property Value, May 2020: ' + props.valmay2020 + '<br />' +
'Median Home Sale Price, March 2020: ' + props.salemarch2020 + '<br />' +
'Median Rent, April 2020: ' + props.rentapr2020 + ', City Level Only' + '<br / >' +
'1yr. House Value Change: ' + props.chg1yrpropval +'%' + '<br />'
: 'Hover over a ZIP Code');
};
info.addTo(map);
// get color depending on population density value
function getColor(d) {
return d > 400000 ? '#800026' :
d > 300000 ? '#BD0026' :
d > 200000 ? '#E31A1C' :
d > 150000 ? '#FC4E2A' :
d > 100000 ? '#FD8D3C' :
d > 50000 ? '#FEB24C' :
d > 25000 ? '#FED976' :
'#FFEDA0';
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.valmay2020)
};
}
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
info.update(layer.feature.properties);
}
var geojson;
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
click: highlightFeature,
mouseout: resetHighlight,
dbblick: zoomToFeature
});
}
var geojson = new L.GeoJSON.AJAX(select.value, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
var statelay = new L.GeoJSON.AJAX(state, {
filter: statefilter
}).addTo(map);
function statefilter(feature) {
if (feature.properties.STUSPS == strUser) return true
}
map.attributionControl.addAttribution('Housing and Rental data © Zillow');
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 25000, 50000, 100000, 150000, 200000, 300000, 400000],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
froml = grades[i]/1000 + 'k';
tol = grades[i +1]/1000 + 'k';
labels.push(
'<i style="background:' + getColor(from + 1) + '"></i> ' +
froml + (to ? '–' + tol : '+'));
}
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
};
ourRequest.send();
});
This is a easy fix.
Your interactivity is gone, because you add the state geojson over the interactivity geojson. Two solutions:
call statelay.bringToBack() when you want to display the area / border
don't add it to the map, if you don't want to show it. Remove .addTo(map) from var statelay = new L.GeoJSON.AJAX(...).addTo(map)
Zoom to the bounds: Wait until the geojson is loaded and the call fitBounds
statelay.on('data:loaded', function() {
map.fitBounds(statelay.getBounds())
})
But you will have a problem with zooming to the bounds for example with AK, because the shape is not correct (But that can't fixed by me):
And this is very lazy because this is a very big file ... so I recommand you a other way to zoom to the area:
zoom to the bounds of the geojson layer, then it is zoomed directly after the interactiv shapes are loaded.
geojson.on('data:loaded', function() {
map.fitBounds(geojson.getBounds())
})

Make Google maps callback wait for rest of functions to finish

I am having difficulty with the way google calls its maps api. I have the following calling initMap
<script defer
src="https://maps.googleapis.com/maps/api/js?key=API_KEY_REMOVED&callback=initMap">
</script>
but inside initMap, the following condition if(getPosition() !== false) { never evaluates to true because init map is done before getPosition() has set its object values.
function initMap() {
// set new map, assign default properties
map = new google.maps.Map(document.getElementById('map'), {
center: { lat, lng }, zoom: 14
});
// check if the requested data is usable (lat, lng === numbers) before trying to use it
if(getPosition() !== false) {
map.setCenter( getPosition() ); // set latest position as the map center
addMarker();
console.log("InitMap ran here");
}
}
How can I make it so initMap waits until getPosition() has had a chance to wait for other functions to do their thing? Here is my complete script so it makes more sense.
<script>
console.log(formatTime(Date()));
// https://developers.google.com/maps/documentation/javascript/geolocation
var map; var marker;
var lat = 65.025984; var lng = 25.470794; // default map location in case no position response is available
var res_data; var res_longitude; var res_latitude; var res_speed; var res_time; // res = response (data from the ajax call)
var xhr = new XMLHttpRequest();
function getPosition() {
pos = {
lat: res_latitude,
lng: res_longitude,
};
return ( isNaN(pos.lat) || isNaN(pos.lng) ) ? false : pos; // return pos only if lat and lng values are numbers
}
function initMap() {
// set new map, assign default properties
map = new google.maps.Map(document.getElementById('map'), {
center: { lat, lng }, zoom: 14
});
// check if the requested data is usable (lat, lng === numbers) before trying to use it
if(getPosition() !== false) {
map.setCenter( getPosition() ); // set latest position as the map center
addMarker();
console.log("InitMap ran here");
}
}
// place marker on the map
function addMarker() {
//console.log("Add Marker ran");
//https://developers.google.com/maps/documentation/javascript/markers
if(marker){ marker.setMap(null); } // remove visibility of current marker
marker = new google.maps.Marker({
position: getPosition(),
map: map,
title: formatTime(res_time),
});
marker.setMap(map); // set the marker
}
function getData() {
xhr.addEventListener("load", reqListener);
xhr.open("GET", "http://example.com/data.txt");
xhr.send();
}
function reqListener() {
// res_data = long, lat, accuracy, speed, time
//console.log("reqListener: " + xhr.responseText);
res_data = '[' + xhr.responseText + ']';
res_data = JSON.parse(res_data);
res_latitude = res_data[0]; res_longitude = res_data[1]; res_accuracy = res_data[2]; res_speed = res_data[3]; res_time = res_data[4];
var formatted_time = formatTime(res_time);
document.getElementById('info').innerHTML = '<span class="info">Lat: ' + res_latitude + '</span><span class="info">Long: ' + res_longitude + '</span><span class="info">Accuracy: ' + res_accuracy + '</span><span class="info">Speed: ' + res_speed + '</span><span class="info">' + formatted_time + '</span>';
addMarker();
}
function formatTime(time) {
var t = new Date(time);
var hours, mins, secs;
if(t.getHours() < 10) { hours = "0" + t.getHours(); } else { hours = t.getHours(); }
if(t.getMinutes() < 10) { mins = "0" + t.getMinutes(); } else { mins = t.getMinutes(); }
if(t.getSeconds() < 10) { secs = "0" + t.getSeconds(); } else { secs = t.getSeconds(); }
var hms = hours +':'+ mins +':'+ secs;
return 'Updated: ' + hms;
}
function init() {
getData();
setInterval(getData, 5000);
}
init();
</script>
<script defer
src="https://maps.googleapis.com/maps/api/js?key=API_KEY_REMOVED&callback=initMap">
</script>
Get rid of the callback=initMap from where you load in the Maps API.
Instead make a call to initMap only from where you are then certain everything is loaded. e.g. at the end of reqListener.
function reqListener() {
res_data = '[' + xhr.responseText + ']';
res_data = JSON.parse(res_data);
res_latitude = res_data[0]; res_longitude = res_data[1]; res_accuracy = res_data[2]; res_speed = res_data[3]; res_time = res_data[4];
var formatted_time = formatTime(res_time);
document.getElementById('info').innerHTML = '<span class="info">Lat: ' + res_latitude + '</span><span class="info">Long: ' + res_longitude + '</span><span class="info">Accuracy: ' + res_accuracy + '</span><span class="info">Speed: ' + res_speed + '</span><span class="info">' + formatted_time + '</span>';
initMap();
addMarker();
}
If you're calling reqListener at repeated intervals and don't want to recreate your map, add some logic to the top of initMap like:
if (map !== null) {
return;
}

how to calculate the sum of time inside a for loop in javascript

I'm using a function from google api to draw a way on the map it works good heres the mehode:
calculateAndDisplayRoute(directionsService, directionsDisplay) {
var waypts = [];
// var jsonData = JSON.parse(this.city);
if (!this.isArrayEmpty(this.stations)) {
for (var i = 0; i < this.stations.length; i++) {
waypts.push({
location: this.stations[i].station,
stopover: true
});
}
}
directionsService.route({
origin: this.depart,
destination: this.arrivee,
waypoints: waypts,
optimizeWaypoints: false,
travelMode: 'DRIVING'
}, function (response, status) {
if (status === 'OK') {
directionsDisplay.setDirections(response);
var route = response.routes[0];
let momo;
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
var routeSegment = i + 1;
alert('coco');
let dt = new Date(route.legs[i].duration.value*1000);
let hr = dt.getHours();
let m = "0" + dt.getMinutes();
let s = "0" + dt.getSeconds();
let durationn= hr+ ':' + m.substr(-2) + ':' + s.substr(-2); //this gives 02:35:45 for exemple
/*
//I tried this this code in comment but it doesnt work.
let time1=durationn;
momo=time1;
let [hours1, minutes1, seconds1] = time1.split(':');
let [hours2, minutes2, seconds2] = momo.split(':');
momo=moment({ hours: hours1, minutes: minutes1, seconds: seconds1 })
.add({ hours: hours2, minutes: minutes2, seconds: seconds2 })
.format('h:mm:ss')
*/
console.log('mm'+ route.legs[i].start_address + ' '+route.legs[i].end_address +' '+route.legs[i].distance.text+' '+route.legs[i].duration.text);
console.log( momo);
}
} else {
window.alert('Directions request failed due to ' + status);
}
});
}
but my proble is on the duration, i will explain, i want every time this fuction get called, i need the duration to be sumed(sum of the duration).
I tried this this code in comment but it doesnt work.
PS:
please dont lose your time to understand the code just focus what is inside the second for loop block.
You can do something like that:
var sum = {h: 0, m: 0, s: 0};
var allTimes = ["02:35:45", "11:40:30", "12:55:39"];
allTimes.forEach(time => sum = sumUp(sum, time)); // Just sum up hour, minute and second
console.log(parseSum(sum)); // Do formatting in the end
function sumUp(sum, time) {
var times = time.split(":");
sum.h += +times[0];
sum.m += +times[1];
sum.s += +times[2];
return sum;
}
function parseSum(sum) {
var totSec = sum.s;
var s = totSec % 60;
var totMin = sum.m + parseInt(totSec/60);
var m = totMin % 60;
var totHour = sum.h + parseInt(totMin/60);
var h = totHour;
return `${h}:${m}:${s}`;
}

Multiple direction requests take more time after multiple calls

Let's say you have a map to display KmlLayer objects on it, with toggle checkbox for each KmlLayer object, located in a panel.
var Province = new google.maps.LatLng(47.112754, -70.815223);
var map = new google.maps.Map( document.getElementById('map'), {zoom:7,center:Province} );
And you store in KML files a limited number of routes, to optimize the speed of the map.
Direction's requests are made using Google Maps API v3.
And let's say you have a page containing a Google Map in a hidden div, and you make Direction requests through this API.
The array 'waypoints' holds the coordinates of each start and end point of each polyline needed.
var geo_coords = ''; // holds the results
var y; // counter
var delay_per_request = 1000; // default delay in milliseconds between requests
var current_delay = delay_per_request; // current delay between requests
function calcRoute(start,end) {
setTimeout(function(){
var directionsService = new google.maps.DirectionsService();
var request = {
origin: start,
destination: end,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
// Error?
if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
document.getElementById('error_text').innerHTML += "<br>" + start + "/" + end
+ "("+status+") :: Interval:"+current_delay+"ms";
window.scrollTo(0, document.body.scrollHeight);
if (current_delay < 2600) {
current_delay += 200;
}
if (current_delay >= 1600) {
delay_per_request = 1600;
}
retry++;
if (retry < 20) {
calcRoute(start,end);
} else {
document.getElementById('error_text').innerHTML += "<br>The script stopped running after "+retry+" retries"
+ " sur " + start + "/" + end + "("+status+") :: Interval:"+current_delay+"ms";
}
// Positive result?
} else if (status == google.maps.DirectionsStatus.OK) {
var myRoute = response.routes[0].overview_path;
for (i in myRoute) {
if (myRoute[i] != undefined) {
geo_coords += myRoute[i].lat() + "," + myRoute[i].lng() + "_";
}
}
geo_coords += "!";
document.getElementById('error_text').innerHTML += "<br><font color=\"green\">" + start + "/" + end
+ "("+status+") :: Interval:"+current_delay+"ms</font>";
window.scrollTo(0, document.body.scrollHeight);
current_delay = delay_per_request;
retry = 0;
Next();
}
});
},current_delay);
}
function Next() {
if (y <= waypoints.length) {
destination = "from:" + waypoints[y] + " to:" + waypoints[y + 1];
geo_desc += description[y] + " => " + description[y+1] + "|";
calcRoute(waypoints[y],waypoints[y+1]);
y = y +2;
} else {
document.getElementById("information").setAttribute('value',geo_coords);
document.getElementById("show_debug_text").setAttribute('value',geo_desc);
document.getElementById("error").setAttribute('value',document.getElementById('error_text').innerHTML);
document.forms["kml"].submit();
}
}
Next();
My question is the following:
Has anyone noticed that Google lower the priority of your requests after you made some request in batch?
The more request you do, the bigger is the number of OVER_QUERY_LIMIT response you get, the bigger is the delay between requests before you have a successful result, and the bigger is the number of requests before you have a successful result.
Is there any workaround or any reason?
(I know I should use the Direction API Webservice to make my requests.)
Thank you!

Building Drop Down Options from Object

I have a landing page I'm working on, where I want the user to select a state, and then when the state is selected, the select dropdown below it will show the locations within that state. The list of locations comes from a json file where there is a list of store locations, with their attributes such as store name, state, etc. I've created an object, but I'm not sure how to populate the select based on the state. Also, the way I'm building the list of states may not be the best way either, so any help there would be great also. Thanks!
$(document).ready(function(){
var buildLocations = {
'settings': {
directoryListingItems : {},
directoryListingArray : [],
globalLatLong : null,
globalLatitude : geoip_latitude(),
globalLongitude : geoip_longitude(),
globalCity : geoip_city(),
globalState : geoip_region_name(),
locationRadius : 30,
NearbyLocationsCount : 0,
locationTotalPlaceholder: $('#location-number'),
locationNamePlaceholder : $('#location-name'),
stateDropdownArray : [],
stateDropdown : $('#state'),
locationDropdownArray : [],
locationDropdown : $('#location'),
},
init: function() {
bLs = this.settings;
buildLocations.getJSONLocations();
},
getJSONLocations: function() {
$.ajax({
url: "data/data.json",
dataType: "JSON",
success: buildLocations.getLocations
});
},
getLocations: function(results) {
// creating objects
for(var i = 0; i < results.locations.length; i++) {
bLs.directoryListingItems = {
store_id: results.locations[i].storeid,
title: results.locations[i].title,
latitude: results.locations[i].latitude,
longitude: results.locations[i].longitude,
state: results.locations[i].state,
distance: buildLocations.getLocationDistance(bLs.globalLatitude, bLs.globalLongitude, results.locations[i].latitude, results.locations[i].longitude)
};
bLs.directoryListingArray.push(bLs.directoryListingItems);
//Check if a state is already in the states array, if not, add it
if ($.inArray('<option value=\"' + bLs.directoryListingArray[i].state + '\">'+ bLs.directoryListingArray[i].state + '</option>', bLs.stateDropdownArray)==-1) {
bLs.stateDropdownArray.push('<option value=\"' + bLs.directoryListingArray[i].state + '\">'+ bLs.directoryListingArray[i].state + '</option>');
//alert("added"+ value.state);
}
//get selected state value
//if in state in bLs.directoryListingItems array matches this value, add item to array
//Add Each location to the locations dropdown
bLs.locationDropdownArray.push('<option value=\"' + bLs.directoryListingArray[i].storeid + '\">'+ bLs.directoryListingArray[i].title + '</option>');
//Count the number of locations that are within the set location radius of the users detected location
if (bLs.directoryListingArray[i].distance < bLs.locationRadius) {
bLs.NearbyLocationsCount++;
}
}
//Sort the states array in alphabetical order
bLs.stateDropdownArray.sort();
//run function to populate dropdowns
buildLocations.populateDOM();
},
compareDistances: function(a,b) {
if (a.distance < b.distance)
return -1;
if (a.distance > b.distance)
return 1;
return 0;
},
populateDOM: function() {
//populate the number inside the map marker
bLs.locationTotalPlaceholder.text(bLs.NearbyLocationsCount);
//populate the area next to the map marker with the users location and state
bLs.locationNamePlaceholder.text(bLs.globalCity + ", " + bLs.globalState);
//build state select dropdown
bLs.stateDropdown.html(bLs.stateDropdownArray);
buildLocations.populateDOMlocations();
},
populateDOMlocations: function() {
//$.each(bLs.directoryListingItems, function(index, value) {
//if (value.state="Florida") {
//alert(index)
///}
//});
//$.each(bLs.directoryListingItems, function(index, obj) {
//$.each(obj, function(attr, value) {
// console.log( attr + ' == ' + value );
//});
//});
//build locations select dropdown
bLs.locationDropdown.html(bLs.locationDropdownArray);
},
getLocationDistance : function(lat1,lon1,lat2,lon2) {
function deg2rad(deg) {
return deg * (Math.PI/180)
};
var R = 6371; // Radius of the earth in km
var dLat = deg2rad(lat2-lat1); // deg2rad below
var dLon = deg2rad(lon2-lon1);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = (R * c) * 0.6214; // Distance in miles
return Math.round( d * 10 ) / 10
},
};
// ====================================================== //
// Populate Locations
buildLocations.init();
});
I figured it out,
populateDOMlocations: function() {
bLs.currState = bLs.stateDropdown.val();
bLs.locationDropdownArray = [];
for(var l = 0; l < bLs.directoryListingArray.length; l++) {
if (bLs.directoryListingArray[l].state == bLs.currState ) {
bLs.locationDropdownArray.push('<option value=\"' + bLs.directoryListingArray[l].storeid + '\">'+ bLs.directoryListingArray[l].title + '</option>');
}
}
bLs.locationDropdownArray.sort();
//build locations select dropdown
bLs.locationDropdown.html(bLs.locationDropdownArray);
},

Categories

Resources