Friday, March 16, 2018

Old Phone/Tablet as an Info Board Part 2: Direct API queries

In the previous post, we discussed using iframe for an information widget on our info board, noting that it would be too heavy on your browser if your target web page is feature-rich. This means you can forget anything like a Google maps snippet.

However, precisely with this in mind, many large websites have developed APIs that other websites (read: we) can use. Google does a great job providing its maps API, so incorporating local traffic can be done as simple as

 
<div id="map" class="basic" style="border: none; position: absolute; right: 10px; top: 10px; width: 415px; height: 350px;">
</div>

<script async defer
    src="https://maps.googleapis.com/maps/api/js?key=(get_your_own_key)&callback=initMap">
</script>
 
 <script>
  function initMap() {
     map = new google.maps.Map(document.getElementById('map'), {
       zoom: 13,
        center: {lat: 44.444, lng: -77.777} // Change these parameters to your location
        });

        var trafficLayer = new google.maps.TrafficLayer();
        trafficLayer.setMap(map);
      }
</script>


Obviously to make this work, you will need to get you own Google API key, and change the map location (ans possibly, zoom) to where you would like your map to display. The easiest way to find it would be to open your Google Maps view and then look at the URL to find the values for latitude and longitude.

Now let us take a step further and provide a widget to monitor the duration in traffic of a given trip in real time. For added flavor, we will compute both the travel time in both directions, where the outbound trip is to start now and the return trip would start in the future.

For this, we define the interface

 
<div id="travel" class="basic info" style="position: absolute; left: 150px; bottom: 190px; width: 415px; height: 60px; background:rgb(255,255,225);">
<span id="log1" style="font-size: 10px; color:rgb(200,200,200); position: relative; top: 0px;">Not loading</span><br/>
<span id="log2" style="font-size: 10px; color:rgb(200,200,200); position: relative; top: 2px;">Not loading</span><br/>
<span id="results" style="position: relative; top: 10px;"><span id="result1" style="font-weight: bold;">. . .</span> / <span id="result2" style="font-weight: bold;">. . .</span> <span id="resultkm">...</span></span>
</div>


and poll the Directions API like this:

 
function navigate(){
 $("#log1").text("load...");$("#log2").text("load...");
 var origin = "44.44444, -77.77777";
 var destination = "43.33333,-78.88888";
 //Change these to your origin and destination

 var directionsService = new google.maps.DirectionsService();
 // var directionsDisplay = new google.maps.DirectionsRenderer();
 // directionsDisplay.setMap(map)
    var request1 = {
 origin: origin, destination: destination, 
 travelMode: google.maps.DirectionsTravelMode.DRIVING,
 drivingOptions: { departureTime: new Date(Date.now()+0), trafficModel: 'bestguess'}
 };
    var request2 = {
 origin: destination, destination: origin, // return trip
 travelMode: google.maps.DirectionsTravelMode.DRIVING,
 drivingOptions: { departureTime: new Date(Date.now()+1000*60*30), trafficModel: 'bestguess'}
        // trip stars 30 minutes in the future
 };
   var fAlert = "rgb(200, 0, 200)";
   var fWarn = "rgb(255, 0, 0)";
   var bOK = "rgb(225,255,225)";
   var bAlert = "rgb(255,255,200)";
   var bWarn = "rgb(255,180,180)";
     
   directionsService.route( request1, function( response, status ) {
    if ( status === 'OK' ) {
        // directionsDisplay.setDirections(response);
        var point = response.routes[ 0 ].legs[ 0 ];
 var d=new Date();
 $( '#log1' ).html(d.toString());
        $( '#result1' ).html(point.duration_in_traffic.text);
    $( '#resultkm' ).html(' (' + point.distance.text + ')' );
 if (point.duration_in_traffic.value > 16*60) {$("#result1").css("color",fAlert);};
 if (point.duration_in_traffic.value > 25*60) {$("#result1").css("color",fWarn);};
 $("#travel").css("background-color",bOK);
 if ($("#result1").css("color")==fAlert) {$("#travel").css("background-color",bAlert);};
 if ($("#result1").css("color")==fWarn || $("#result2").css("color")==fWarn ) {$("#travel").css("background-color",bWarn);};
    }
} ); 
   directionsService.route( request2, function( response, status ) {
    if ( status === 'OK' ) {
        var point = response.routes[ 0 ].legs[ 0 ];
 var d=new Date();
 $( '#log2' ).html(d.toString());
        $( '#result2' ).html(point.duration_in_traffic.text);
 if (point.duration_in_traffic.value > 16*60) {$("#result2").css("color",fAlert);};
 if (point.duration_in_traffic.value > 25*60) {$("#result2").css("color",fWarn);};
    }
} ); 
}
Note that as per Google's use policies, you will need to display the directions and route on a map if you use this function on a website that other people can see - just uncomment the corresponding DirectionsRenderer code lines. For your own use, and at your own risk, I believe you can forego this for the sake of readability if you know what you are doing. There is a caveat, though, in that you won't know the exact route that the time was calculated for.

Here are the examples, showing three possible traffic situations:
As usual, your mileage may vary:
  • You can implement more sophisticated logic of coloring and alerts - an obvious candidate would be analyzing the difference between .duration and .duration_in_traffic
  • You can analyze the suggested best route and alert if it happens to be different from the default route (which you can determine by making a request when there is as little traffic as possible, i.e. in the middle of the night) - this will indicate a major congestion.
  • You can compare travel time along a predetermined route (by setting many waypoints in the most congested portion) and the chosen preferred route;
  • You can even log / graph the travel time gathering your own statistics and storing it in an SQL database on the DiskStation. This way, should Google's API become unavailable, you will have a fallback method to determine travel times based on historical values. 
As far as the billing goes... Even if we poll the server every minute round the clock (which is totally overkill), we still won't exceed Google free usage quotas. And even if we did, once debugged and calibrated, this is such a useful service that you may seriously consider paying for it.