Advanced InfoWindow Widget Driving and Directions

IntroductionAPI

Drive Directions Widget

Directions pane is designed for single or multi-waypoint directions. CMaps Analytics directions pane uses Google Maps Directions API for up to 8 locations from Advanced InfoWindow.

directions

Selected Locations vs Last Saved

Selected locations provides a fast method to autoroute to multiple destinations.

LAST SAVED: CMaps Analytics view will cache the last generate directions. If the same CMaps Analyics map is opened at a future date, the saved directions will be available from the local browser or device cache. The caching using HTML5 SQLite to store these directions, which are not transmitted to CMaps Analytics cloud.

Start and End Points:

The start and end points can be filled as current location, a point on the map, or by simply clicking on any point inside of the map.

Capture8

Multi-Waypoints:

CMaps Designer Advanced InfoWindow currently supports up to 8 Waypoints which can be re-ordered at any time independent of map selections. A waypoint can also be set to the device / browser current location, or clicking on the map.

Open in Google Maps:

The route manager offers a single click export option to open routes in Google Maps

API Details

Card Markup and CSS


<div>

 <div class="com_cmapsanalytics_cardheader com_cmapsanalytics_card_innerbox">
 <div class="com_cmapsanalytics_alignleft com_cmapsanalytics_card_title">
 Directions
 </div>
 </div>
 
 <div id="dirsSubContainer" style="margin-right: 5px;padding: 10px;">
 
 <div class="com_cmapsanalytics_actionheaderlinks">
 <a id="btnGetDirections">Get Directions</a>&nbsp;|&nbsp;
 <a id="btnClearDirections">Clear</a>&nbsp;|&nbsp;
 <a id="googleDirsUrl" target="_blank">Open in Google</a>
 </div>

 <div>
 
 <div class="com_cmapsanalytics_autofilldirscontainer">
 <a id="btnAutofillDirections" class="com_cmapsanalytics_autofilldirslink">Autofill</a>
 <br/>
 <div class="com_cmapsanalytics_horiznav com_cmapsanalytics_dirsprefilltoggles">
 <ul>
 <li data-prefilltype="selectedlocations"><a id="selectedLocations" class="active">Selected Locations</a></li>
 <li data-prefilltype="saveddirections"><a id="savedDirections">Last Saved</a></li>
 </ul>
 </div>
 </div>
 <label id="lblTravelTime">Travel time & distance: </label>
 <div class="com_cmapsanalytics_dirsstartend">
 <label>Start</label>
 <select id="selDirsStart" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist"></select>
 <label>End</label>
 <select id="selDirsEnd" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist"></select>
 </div>
 <p/>
 
 <label>Waypoints</label>
 
 <div class="com_cmapsanalytics_dirswaypoints">
 <label>1. </label>
 <select id="selWaypoint1" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <label>5. </label>
 <select id="selWaypoint5" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <br/>
 <label>2. </label>
 <select id="selWaypoint2" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <label>6. </label>
 <select id="selWaypoint6" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <br/>
 <label>3. </label>
 <select id="selWaypoint3" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <label>7. </label>
 <select id="selWaypoint7" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <br/>
 <label>4. </label>
 <select id="selWaypoint4" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 <label>8. </label>
 <select id="selWaypoint8" class="com_cmapsanalytics_sel_ctrl com_cmapsanalytics_directionslist">
 <option value="directions">Select...</option>
 </select>
 </div>
 </div>
 </div>
</div>


<style type="text/css">

.com_cmapsanalytics_directionslist{

}

.com_cmapsanalytics_dirsprefilltoggles{
 position: absolute;
 top: 0px;
}

.com_cmapsanalytics_autofilldirscontainer{
 height: 50px;
 margin-bottom: 10px;
 position: relative;
}

.com_cmapsanalytics_dirswaypoints{
 margin-top: 15px;
}

.com_cmapsanalytics_dirswaypoints select{
 width: 100px;
 margin-left: 10px;
 margin-right: 10px;
 margin-bottom: 5px;
}

.com_cmapsanalytics_dirsstartend{
 margin-top: 15px;
}

.com_cmapsanalytics_dirsstartend select{
 width: 85px;
 margin-left: 10px;
 margin-right: 10px;
}

.com_cmapsanalytics_autofilldirslink{
 position: absolute;
 right: 5px;
 top: 15px;
}

</style>

JavaScript


(function(){

 createNamespaceUnderCentigon("ui.DirectionsCard");

 centigon.ui.DirectionsCard = function(){
 
 centigon.ui.Card.call(this);
 
 this.contentUrl = this.getCardAssetUrl() + "DirectionsCardMarkup.txt";
 this.controlIdToRandomId = {
 dirsSubContainer: this._domUtil.getRandomDivId(),
 lblTravelTime: this._domUtil.getRandomDivId(),
 lblTravelDistance: this._domUtil.getRandomDivId(),
 selDirsAutofill: this._domUtil.getRandomDivId(),
 btnAutofillDirections: this._domUtil.getRandomDivId(),
 selDirsStart: this._domUtil.getRandomDivId(),
 selDirsEnd: this._domUtil.getRandomDivId(),
 btnGetDirections: this._domUtil.getRandomDivId(),
 btnClearDirections: this._domUtil.getRandomDivId(),
 googleDirsUrl: this._domUtil.getRandomDivId(),
 selectedLocations: this._domUtil.getRandomDivId(),
 savedDirections: this._domUtil.getRandomDivId(),
 selWaypoint1: this._domUtil.getRandomDivId(),
 selWaypoint2: this._domUtil.getRandomDivId(),
 selWaypoint3: this._domUtil.getRandomDivId(),
 selWaypoint4: this._domUtil.getRandomDivId(),
 selWaypoint5: this._domUtil.getRandomDivId(),
 selWaypoint6: this._domUtil.getRandomDivId(),
 selWaypoint7: this._domUtil.getRandomDivId(),
 selWaypoint8: this._domUtil.getRandomDivId() 
 };
 
 this._dirsDb;
 try{
 if(window.openDatabase){
 
 var shortName = 'db_cmapsanalytics_directions';
 var version = '1.0';
 var displayName = 'CMapsAnalytics Directions';
 var maxSize = 5000000;//fiveMBInBytes
 this._dirsDb = openDatabase(shortName, version, displayName, maxSize);
 }
 }
 catch(e){
 console.log(e);
 }
 
 this._dirsLocs = [];
 
 this._acceptingWaypointTimer;
 this._acceptingWaypointFromMapClick = false;
 this._dropdownAcceptingWaypointFromMapClick;
 this._dirsLayerIx = -1;
 }
 
 centigon.ui.DirectionsCard.constructor = centigon.ui.Card;
 centigon.ui.DirectionsCard.prototype = new centigon.ui.Card();
 
 centigon.ui.DirectionsCard.prototype.getHtml = function(gotHtml){
 
 this.getRemoteContent({url: this.contentUrl, success: gotHtml});
 }
 
 centigon.ui.DirectionsCard.prototype.addEventListeners = function(){
 
 var that = this;
 
 var prefillType = "selectedlocations";
 
 this.domObjByRandField('googleDirsUrl').hide();
 
 var btnOpts = [{id: this.controlIdToRandomId["selectedLocations"]},
 {id: this.controlIdToRandomId["savedDirections"]}];
 
 var toggleListMenu = new centigon.ui.ToggleListMenu(btnOpts);
 toggleListMenu.change = function(domElem){
 
 prefillType = that._domUtil.getDomObjectDataAttribute(domElem.parentElement, "prefilltype");
 }
 
 this.domObjByRandField("btnGetDirections").click(function(){
 that.getDirections();
 });
 
 this.domObjByRandField("btnClearDirections").click(function(){
 that.clearDirections();
 });
 
 this.domObjByRandField("btnAutofillDirections").click(function(){
 that.populateDirectionsControls(prefillType);
 });
 
 this.getDomObjByClassNameUnderMyDiv(".com_cmapsanalytics_directionslist").change(function(){
 
 if(that._domUtil.castAsDomObject(this).val().toLowerCase() === "clickmap"){
 that._dropdownAcceptingWaypointFromMapClick = that._domUtil.castAsDomObject(this);
 that._acceptingWaypointFromMapClick = true;
 that._acceptingWaypointTimer = setTimeout(function(){that._acceptingWaypointFromMapClick = false;}, 15000)
 }
 });
 
 this.cMap.addMapClickListener(function(location){
 
 if(that._acceptingWaypointFromMapClick === true && that._dropdownAcceptingWaypointFromMapClick){
 
 var csvLatLng = location.toCsvLatLon();
 that._dropdownAcceptingWaypointFromMapClick.append('<option value="'+csvLatLng+'">'+csvLatLng+'</option>');
 that._dropdownAcceptingWaypointFromMapClick.val(csvLatLng);
 
 clearTimeout(that._acceptingWaypointTimer);
 }
 });
 
 this.populateDirectionsControls("selectedlocations");
 }

 centigon.ui.DirectionsCard.prototype.aiwLayerChanged = function(){
 
 this.populateDirectionsControls("selectedlocations");
 }
 
 centigon.ui.DirectionsCard.prototype.updateView = function(){

 this.populateDirectionsControls("selectedlocations");
 }
 
 centigon.ui.DirectionsCard.prototype.formatSecondsToHMS = function (psecs) {
 
 var secs = parseInt(psecs, 10); 
 var hours = Math.floor(secs / 3600);
 var minutes = Math.floor((secs - (hours * 3600)) / 60);
 var seconds = secs - (hours * 3600) - (minutes * 60);

 if (hours < 10) {hours = "0"+hours;}
 if (minutes < 10) {minutes = "0"+minutes;}
 if (seconds < 10) {seconds = "0"+seconds;}
 
 return hours+'h:'+minutes+'m:'+seconds+'s';
 }
 
 centigon.ui.DirectionsCard.prototype.clearDirections = function(){
 
 this.domObjByRandField("lblTravelTime").text("Travel time & distance: ");
 this.getDomObjByClassNameUnderMyDiv(".com_cmapsanalytics_directionslist").val("NONE");
 this.domObjByRandField('googleDirsUrl').attr('href', "");
 this.domObjByRandField('googleDirsUrl').hide();
 this.toggleDirectionsLayer(false);
 }
 
 centigon.ui.DirectionsCard.prototype.getDirections = function(){
 
 if(this.cMap.layerTypeIsMarkerOrHeat(this.layer.type) === false){
 return;
 }
 
 this.domObjByRandField('googleDirsUrl').attr('href', "");
 this.domObjByRandField('googleDirsUrl').hide();
 
 var dirs = [];
 dirs.push({latlng: this.domObjByRandField("selDirsStart").val(), label: this.domObjByRandField("selDirsStart").find("option:selected").text()});
 
 var list = this.getDomObjByClassNameUnderMyDiv(".com_cmapsanalytics_directionslist");
 var selCtrl;
 var ctrlVal;
 
 for(var i=0;i<list.length;i++){
 selCtrl = this._domUtil.castAsDomObject(list[i]);
 ctrlVal = selCtrl.val().toLowerCase();
 if(ctrlVal !== "none" && ctrlVal !== ""){
 dirs.push({latlng: this._domUtil.castAsDomObject(selCtrl).val(), label: this._domUtil.castAsDomObject(selCtrl).find("option:selected").text()});
 }
 }
 
 dirs.push({latlng: this.domObjByRandField("selDirsEnd").val(), label: this.domObjByRandField("selDirsEnd").find("option:selected").text()});
 
 this._dirsLocs = dirs;
 
 this.toggleDirectionsLayer(true);
 
 this.commitCurrentDirectionsToLocalDb(JSON.stringify({locations: dirs}));
 }
 
 centigon.ui.DirectionsCard.prototype.populateDirectionsControls = function(autofillType){
 
 if(this.cMap.layerTypeIsMarkerOrHeat(this.layer.type) === false){
 this.domObjByRandField("dirsSubContainer").css("pointer-events", "none");
 this.domObjByRandField("dirsSubContainer").css("opacity", .2);
 return;
 }
 
 
 this.domObjByRandField("dirsSubContainer").css("pointer-events", "all");
 this.domObjByRandField("dirsSubContainer").css("opacity", 1);
 
 var lastLayerRestrictCalcsToSelected = this.layer.restrictCalcsToSelected;
 var lastLayerRestrictCalcsToVisible = this.layer.restrictCalcsToVisible;
 
 this.layer.restrictCalcsToSelected = false;
 this.layer.restrictCalcsToVisible = true;
 
 var list = this.getDomObjByClassNameUnderMyDiv(".com_cmapsanalytics_directionslist");
 var totalNumDirsDropdowns = list.length;
 
 var that = this;
 
 var setDirsListsHtml = function(locs, labels){
 
 var html = '<option selected value="NONE"></option>';
 html += '<option value="'+that.cMap.currentLocation()+'">Current Location</option>';
 html += '<option value="clickMap">Click the map</option>';
 
 locs = that._utilFactory.getCollectionIterator(locs);
 labels = that._utilFactory.getCollectionIterator(labels);
 
 while(locs.hasNext() && labels.hasNext()){
 html += '<option value="'+locs.next()+'">'+labels.next()+'</option>'; 
 }
 
 that.getDomObjByClassNameUnderMyDiv(".com_cmapsanalytics_directionslist").html(html);
 }
 
 if(autofillType === "selectedlocations"){
 this.layer.restrictCalcsToSelected = true;
 this.layer.restrictCalcsToVisible = true;
 
 var locs = this.getLatLngCsvArrFromLocs(this.layer.getLocations());
 
 setDirsListsHtml(locs, this.layer.getLabels());
 
 var selCtrl;
 
 locs = that._utilFactory.getCollectionIterator(locs);
 while(locs.hasNext() && locs.getPosition() <= totalNumDirsDropdowns){
 //start, end, waypoints
 if(locs.getPosition() === 0){
 this.domObjByRandField("selDirsStart").val(locs.next());
 }
 else if(locs.hasOneLeft() === true){
 this.domObjByRandField("selDirsEnd").val(locs.next());
 }
 else{
 selCtrl = this._domUtil.castAsDomObject(list[locs.getPosition()-1]);
 selCtrl.val(locs.next())
 }
 }
 }
 else if(autofillType === "saveddirections"){
 
 var combinedLocs = this.getLatLngCsvArrFromLocs(this.layer.getLocations());
 var combinedLabels = this.layer.getLabels().slice();
 
 this.getLastDirectionsFromLocalDb(function(storedLocs, storedLabels){
 
 setDirsListsHtml(combinedLocs.concat(storedLocs), combinedLabels.concat(storedLabels));
 
 var selCtrl;
 
 storedLocs = that._utilFactory.getCollectionIterator(storedLocs);
 while(storedLocs.hasNext() && storedLocs.getPosition() <= totalNumDirsDropdowns){
 //start, end, waypoints
 if(storedLocs.getPosition() === 0){
 that.domObjByRandField("selDirsStart").val(storedLocs.next());
 }
 else if(storedLocs.hasOneLeft() === true){
 that.domObjByRandField("selDirsEnd").val(storedLocs.next());
 }
 else{
 selCtrl = that._domUtil.castAsDomObject(list[storedLocs.getPosition()-1]).find("select")[0];
 that._domUtil.castAsDomObject(selCtrl).val(storedLocs.next())
 }
 }
 });
 }
 else{
 setDirsListsHtml(this.getLatLngCsvArrFromLocs(this.layer.getLocations()), this.layer.getLabels());
 }
 
 this.layer.restrictCalcsToSelected = lastLayerRestrictCalcsToSelected;
 this.layer.restrictCalcsToVisible = lastLayerRestrictCalcsToVisible;
 }
 
 centigon.ui.DirectionsCard.prototype.getLatLngCsvArrFromLocs = function(locs){
 
 var latlngs = [];
 
 locs = this._utilFactory.getCollectionIterator(locs);
 
 while(locs.hasNext()){
 latlngs.push(locs.next().toCsvLatLon());
 }
 
 return latlngs;
 }
 
 centigon.ui.DirectionsCard.prototype.getLastDirectionsFromLocalDb = function(callback){
 
 var that = this;
 
 var parseSavedDirs = function(tx, rs){
 
 var rs = rs.rows;
 var dirs = JSON.parse(rs.item(0).dirs);
 
 var labels = [];
 var locs = [];
 
 var objs = that._utilFactory.getCollectionIterator(dirs.locations);
 var obj;
 while(objs.hasNext()){
 obj = objs.next();
 labels.push(obj.label);
 locs.push(obj.latlng);
 }
 
 callback(locs, labels);
 }
 
 this.executeQuery(this._dirsDb, 'SELECT * FROM dirs', [], parseSavedDirs, function(){that._logger.trace("failed to get local dirs")});
 }
 
 centigon.ui.DirectionsCard.prototype.commitCurrentDirectionsToLocalDb = function(dirs){
 
 try{
 
 if(window.openDatabase){
 
 var that = this;
 
 var onSuccess = function(){
 
 var onDeleteSuccess = function(){
 
 that.executeQuery(that._dirsDb, 
 "INSERT INTO dirs (dirs) values(?)", 
 [dirs],
 function(){that._logger.trace("updated local dirs")}, 
 function(){that._logger.trace("failed to update local dirs")}
 )
 }
 
 var onDeleteFailure = function(){
 that._logger.trace("unable to delete from dirs table");
 }
 
 that.executeQuery(that._dirsDb, 'DELETE FROM dirs', [], onDeleteSuccess, onDeleteFailure);
 }
 
 var onFailure = function(){
 that._logger.trace("unable to create dirs table");
 }
 
 this.executeQuery(this._dirsDb, 'CREATE TABLE IF NOT EXISTS dirs(dirs TEXT)', [], onSuccess, onFailure);
 }
 }
 catch(e){
 that._logger.trace("error creating local dirs db");
 }
 }
 
 centigon.ui.DirectionsCard.prototype.executeQuery = function(db, query, sqlArgs, onSuccess, onFailure){
 
 db.transaction(function(transaction){
 transaction.executeSql(query, sqlArgs, onSuccess, onFailure);
 });
 }
 
 centigon.ui.DirectionsCard.prototype.toggleDirectionsLayer = function(visible){
 
 if(visible){
 
 if(this._dirsLayerIx === -1){
 this._dirsLayerIx = this.cMap.locations().length;
 }
 
 var overlayTypes = this.cMap.overlayTypes();
 var names = this.cMap.layerNames();
 var locs = this.cMap.locations();
 
 var objs = this._utilFactory.getCollectionIterator(this._dirsLocs);
 var dirsLocs = [];
 while(objs.hasNext()){
 dirsLocs.push(objs.next().latlng);
 }
 
 overlayTypes[this._dirsLayerIx] = centigon.mapping.Layer.TYPE.DIRECTIONS;
 names[this._dirsLayerIx] = "Directions";
 locs[this._dirsLayerIx] = dirsLocs;

 this.cMap.overlayTypes(overlayTypes);
 this.cMap.layerNames(names);
 this.cMap.locations(locs);
 
 var that = this;
 var numZoomTries = 0;
 var maxNumZoomTries = 50;
 var tryZoomToLyr = function(){
 
 numZoomTries++;
 if(numZoomTries === maxNumZoomTries){
 clearInterval(zmIntrvl);
 }
 
 try{
 
 var lyr = that.cMap.getLayerAt(that._dirsLayerIx)
 if(lyr && lyr.getLocations().length > 0){
 that.cMap.setMapViewportBasedOnLocations(lyr.getLocations());
 
 var trvlTimeLbl = "Travel Time: " + that.formatSecondsToHMS(lyr.travelDurationSeconds);
 var miles = that.getNumericDisplayValue(that.cMap.metersToMiles(lyr.travelDistanceMeters));
 var km = that.getNumericDisplayValue(that.cMap.metersToKm(lyr.travelDistanceMeters));
 
 var trvlDistLbl = "Distance: " + miles + "mi " + km + "km";
 
 that.domObjByRandField("lblTravelTime").text(trvlTimeLbl + " " + trvlDistLbl);
 
 that.domObjByRandField('googleDirsUrl').attr('href', lyr.directionsUrl);
 that.domObjByRandField('googleDirsUrl').show();
 
 clearInterval(zmIntrvl);
 }
 }
 catch(e){
 
 }
 }
 var zmIntrvl = setInterval(tryZoomToLyr ,500);
 }
 else{
 if(this._dirsLayerIx > -1){
 this.cMap.removeLayer(this._dirsLayerIx);
 }
 }
 }
 
 centigon.ui.DirectionsCard.prototype.getNumericDisplayValue = function(val){
 
 if(isNaN(val) === true || isFinite(val) === false){
 return "[No Data]";
 }
 else{
 return this._strUtil.getFormattedAbbreviatedDisplayValue(val);
 }
 }

})()