5 * Navigation application allows user to use Google Maps to navigate around in predefined set of waypoints in Places library with Text-To-Speech feature
6 * (via {{#crossLink "Speech"}}{{/crossLink}} object). Nvigation doesn't use GPS but updates position of marker along the route in periodic intervals.
7 * Default navigation waypoints are defined by:
9 * * start point: {{#crossLink "NavigationGoogle/originAddress:property"}}{{/crossLink}} property
10 * * end point: {{#crossLink "NavigationGoogle/destinationAddress:property"}}{{/crossLink}} property
12 * To add additional places or update existing ones please change property {{#crossLink "NavigationGoogle/places:property"}}{{/crossLink}}.
14 * Hover and click on elements in images below to navigate to components of Navigation application.
16 * <img id="Image-Maps_1201312180420487" src="../assets/img/navigation.png" usemap="#Image-Maps_1201312180420487" border="0" width="650" height="1148" alt="" />
17 * <map id="_Image-Maps_1201312180420487" name="Image-Maps_1201312180420487">
18 * <area shape="rect" coords="0,0,573,78" href="../classes/topbaricons.html" alt="Top bar icons" title="Top bar icons" />
19 * <area shape="rect" coords="0,994,644,1147" href="../classes/bottompanel.html" alt="Bottom panel" title="Bottom panel" />
20 * <area shape="rect" coords="521,133,645,186" href="../classes/Library.html" alt="Places library" title="Places library" />
21 * <area shape="poly" coords="1,78,512,80,514,191,648,192,646,291,0,292," href="../classes/NavigationGoogle.html#method_updateNavigationPanel" alt="Navigation panel" title="Navigation panel" />
22 * <area shape="poly" coords="1,298,648,301,644,890,530,887,528,970,648,969,647,1022,3,1022," href="../classes/NavigationGoogle.html#method_animate" alt="Animate along waypoints" title="Animate along waypoints" />
23 * <area shape="rect" coords="530,888,631,965" href="../classes/NavigationGoogle.html#method_switchMapSatelitteView" alt="Switch map mode" title="Switch map mode" />
25 * @module NavigationGoogleApplication
26 * @main NavigationGoogleApplication
27 * @class NavigationGoogle
31 * Reference to instance of class object this class is inherited from dataModel {@link CarIndicator}.
32 * @property carInd {Object}
37 * Array of destination addresses used in places library list.
38 * @property places {Object[String]}
39 * @for NavigationGoogle
42 {destinationAddress: "Golden Gate Bridge, San Francisco, CA 94129, USA"},
43 {destinationAddress: "Coit Tower, San Francisco, CA 94133, USA"},
44 {destinationAddress: "The Exploratorium, Pier 15, San Francisco, CA 94111, USA"},
45 {destinationAddress: "Steinhart Aquarium, 55 Music Concourse Drive, San Francisco, CA 94122, USA"}
49 * Holds origin address value.
50 * @for NavigationGoogle
51 * @property originAddress {String}
53 var originAddress = "Fell Street, San Francisco, CA, United States";
56 * Holds destination address value.
57 * @for NavigationGoogle
58 * @property destinationAddress {String}
60 var destinationAddress = "75 Hagiwara Tea Garden Dr San Francisco, CA 94118";
63 * Holds origin object value.
64 * @for NavigationGoogle
66 * @property origin {String}
71 * Holds destination object value.
72 * @for NavigationGoogle
74 * @property destination {String}
76 var destination = null;
80 * @for NavigationGoogle
82 * @property map {Object}
87 * Holds geocoder object.
88 * @for NavigationGoogle
90 * @property geocoder {Object}
95 * Holds direction service method.
96 * @for NavigationGoogle
98 * @property directionsService {Object}
100 var directionsService = null;
103 * Holds direction renderer method.
104 * @for NavigationGoogle
106 * @property directionRenderer {Object}
108 var directionRenderer = null;
111 * Holds polyline method.
112 * @for NavigationGoogle
114 * @property polyline {Object}
119 * Holds route object method.
120 * @for NavigationGoogle
122 * @property route {Object}
127 * Holds marker object.
128 * @for NavigationGoogle
130 * @property marker {Object}
135 * Holds direction instructions objects array.
136 * @for NavigationGoogle
138 * @property instructions {Object[]}
140 var instructions = [];
143 * Holds status if metric system is used.
144 * @for NavigationGoogle
146 * @property useMetricSystem {Boolean}
148 var useMetricSystem = false;
151 * Holds step value in meters.
152 * @for NavigationGoogle
154 * @property step {Integer}
156 var step = 10; // metres
159 * Holds tick value in milliseconds.
160 * @for NavigationGoogle
162 * @property tick {Integer}
164 var tick = 200; // milliseconds
167 * Holds distance value.
168 * @for NavigationGoogle
170 * @property distance {Integer}
175 * Holds remaining distance value.
176 * @for NavigationGoogle
178 * @property remainingDistance {Integer}
180 var remainingDistance = 0;
183 * Holds remaining step distance value.
184 * @for NavigationGoogle
186 * @property remainingStepDistance {Integer}
188 var remainingStepDistance = 0;
191 * Holds remaining time value.
192 * @for NavigationGoogle
194 * @property remainingTime {Integer}
196 var remainingTime = 0;
199 * Holds route duration value.
200 * @for NavigationGoogle
202 * @property routeDuration {Integer}
204 var routeDuration = 0;
207 * Holds average speed value.
208 * @for NavigationGoogle
210 * @property averageSpeed {Integer}
212 var averageSpeed = 0;
215 * Holds instruction index value.
216 * @for NavigationGoogle
218 * @property instructionIndex {Integer}
220 var instructionIndex = 0;
223 * Holds status value if instruction changed.
224 * @for NavigationGoogle
226 * @property instructionChanged {Boolean}
228 var instructionChanged = true;
231 * Holds animation timer object.
232 * @for NavigationGoogle
234 * @property timerHandle {Object}
239 * Instance of class Bootstrap, this class provides unified way to boot up the HTML applications by loading shared components in proper order.
240 * * {{#crossLink "Bootstrap"}}{{/crossLink}}
242 * @property bootstrap {Object}
247 * Strips the HTML code removing all the HTML marks from it.
249 * @for NavigationGoogle
251 * @param html {String} HTML code
253 function strip(html) {
255 var tmp = document.createElement("DIV");
256 tmp.innerHTML = html;
257 return tmp.textContent || tmp.innerText;
260 * Method switches between satellite or classic map view.
261 * @method switchMapSatelitteView
262 * @for NavigationGoogle
264 function switchMapSatelitteView() {
267 if (map.getMapTypeId() === google.maps.MapTypeId.ROADMAP) {
268 map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
269 $(".mapIcon").css('display', 'none');
270 $(".satelitteIcon").css('display', 'inherit');
271 } else if (map.getMapTypeId() === google.maps.MapTypeId.SATELLITE) {
272 map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
273 $(".satelitteIcon").css('display', 'none');
274 $(".mapIcon").css('display', 'inherit');
276 console.log("Othere map type is " + map.getMapTypeId());
278 console.log(map.getMapTypeId());
282 * NMethod changes navigation arrow based on instruction text.
283 * @method changeNavigationArrow
284 * @for NavigationGoogle
285 * @param instructionText {String} Instruction text.
287 function changeNavigationArrow(instructionText) {
289 var turnRight = instructionText.indexOf("right");
290 var turnLeft = instructionText.indexOf("left");
292 if (turnRight > 0 && turnRight < 10) {
293 //"left" or "right" is on the beginning of instruction step
294 $("#turnArrow").css("background-image", "url('images/icon_arrow_right.png')");
295 } else if (turnLeft > 0 && turnLeft < 10) {
296 //"left" or "right" is on the beginning of instruction step
297 $("#turnArrow").css("background-image", "url('images/icon_arrow_left.png')");
299 $("#turnArrow").css("background-image", "url('images/icon_arrow_straight.png')");
304 * Method changes format of distance value text.
305 * @method formatMeters
306 * @for NavigationGoogle
307 * @param meters {Integer} Distance value in meters.
308 * @param fontSize {Integer} Font size.
309 * @return {String} Distance in meters width changed font format.
311 function formatMeters(meters, fontSize) {
314 return (Math.round(meters / 100)) / 10 + "<span class=" + fontSize + ">KM</span>";
316 return Math.round(meters) + "<span class=" + fontSize + ">m</span>";
321 * Method converts distance from meters to feets and changing formats of distance value text.
322 * @method convertMetersToFeetsMiles
323 * @for NavigationGoogle
324 * @param meters {Integer} Distance value in meters.
325 * @param fontSize {Integer} Font size.
326 * @return {String} Distance in feets width changed font format.
328 function convertMetersToFeetsMiles(meters, fontSize) {
330 var feets = meters * 3.280839895;
332 return (Math.round(feets / 528)) / 10 + "<span class=" + fontSize + ">MI</span>";
334 return Math.round(feets) + "<span class=" + fontSize + ">ft</span>";
339 * Method converts seconds to time.
340 * @method secondsToTime
341 * @for NavigationGoogle
342 * @param secs {Integer} Time value in seconds.
343 * @return {Object} Time in object with hours, minutes and second separated format.
345 function secondsToTime(secs) {
347 var hours = Math.floor(secs / (60 * 60));
349 var divisorForMinutes = secs % (60 * 60);
350 var minutes = Math.floor(divisorForMinutes / 60);
352 var divisorForSeconds = divisorForMinutes % 60;
353 var seconds = Math.ceil(divisorForSeconds);
364 * Method provides leading 0 to time value.
365 * @method addLeading0ToTime
366 * @for NavigationGoogle
367 * @param time {Integer} Time value.
368 * @return time {String} Time with leading 0.
370 function addLeading0ToTime(time) {
380 * Method formats time to HHMM format.
381 * @method formatTimeToHHMM
382 * @for NavigationGoogle
383 * @param seconds {Integer} Time value in seconds.
384 * @return formatedTime {String} Time in format HHMM.
386 function formatTimeToHHMM(seconds) {
388 var hours = secondsToTime(seconds).h;
389 var minutes = secondsToTime(seconds).m;
392 if (hours > 0 || minutes > 0) {
393 formatedTime = addLeading0ToTime(hours) + ":" + addLeading0ToTime(minutes);
395 formatedTime = seconds + "<span class='fontSizeSmall'>s</span>";
401 * Method adds seconds to current time.
402 * @method addSecondsToCurrentTime
403 * @for NavigationGoogle
404 * @param secs {Integer} Time value in seconds.
405 * @return result {String} Time in format HHMM.
407 function addSecondsToCurrentTime(secs) {
409 var todayDate = new Date();
410 var hours = todayDate.getHours();
411 var minutes = todayDate.getMinutes();
412 var seconds = todayDate.getSeconds();
413 var newSec = parseInt(seconds, 10) + parseInt(secs, 10);
421 mins = parseInt(newSec / 60, 10);
422 sec = newSec - mins * 60;
423 newMin = parseInt(minutes, 10) + mins;
426 var hrs = parseInt(newMin / 60, 10);
427 min = newMin - (hrs * 60);
428 newHrs = parseInt(hours, 10) + hrs;
445 newHrs = newHrs - 12;
454 return newHrs + ":" + min + format;
458 * Method update the navigation panel.
459 * @method updateNavigationPanel
460 * @for NavigationGoogle
462 function updateNavigationPanel() {
464 if (instructionChanged) {
465 var instruction = strip(instructions[instructionIndex].instruction);
466 console.log("Instruction changed to '" + instruction + "'.");
467 $("#navigationPanel").html(instruction);
469 Speech.vocalizeString(instruction);
470 changeNavigationArrow(instruction);
471 instructionChanged = false;
474 if (useMetricSystem === true) {
475 remainingStepDistance = formatMeters(remainingStepDistance, 'fontSizeSmaller');
477 remainingStepDistance = convertMetersToFeetsMiles(remainingStepDistance, 'fontSizeSmaller');
480 $("#distanceTo").html(remainingStepDistance);
481 $("#destinationProgress").progressBarPlugin('setPosition', (remainingDistance / polDistance) * 100);
483 var remainingTime = Math.round(remainingDistance / averageSpeed); //time in seconds
484 remainingTime = formatTimeToHHMM(remainingTime); //seconds to hh:mm
486 if (useMetricSystem === true) {
487 remainingDistance = formatMeters(remainingDistance, 'fontSizeSmall');
489 remainingDistance = convertMetersToFeetsMiles(remainingDistance, 'fontSizeSmall');
492 $("#stillToGoTimeAndDistance").html(remainingTime + " / " + remainingDistance);
496 * Method animates current position along the route.
498 * @for NavigationGoogle
501 function animate(d) {
503 if (d + step > instructions[instructionIndex].distanceValue) {
504 if (instructionIndex < instructions.length - 1) {
505 instructionChanged = true;
510 if (d > polDistance) {
511 instructionIndex = instructions.length - 1;
512 instructionChanged = false;
514 remainingDistance = 0;
515 remainingStepDistance = 0;
518 updateNavigationPanel();
520 map.panTo(polyline.getPath().getAt(polyline.getPath().length - 1));
521 marker.setPosition(polyline.getPath().getAt(polyline.getPath().length - 1));
523 var option = {draggable: true};
524 map.setOptions(option);
528 remainingDistance = polDistance - d;
529 remainingStepDistance = instructions[instructionIndex].distanceValue - d;
531 updateNavigationPanel();
533 var p = polyline.GetPointAtDistance(d);
535 marker.setPosition(p);
536 timerHandle = setTimeout("animate(" + (d + step) + ")", tick);
540 * Method starts animation.
541 * @method startAnimation
542 * @for NavigationGoogle
545 function startAnimation() {
550 map.setOptions(option);
552 marker = new google.maps.Marker();
554 marker.setPosition(polyline.getPath().getAt(0));
555 map.setCenter(polyline.getPath().getAt(0));
556 polDistance = polyline.Distance();
558 console.log(polyline.getPath());
559 console.log(polDistance);
560 console.log(instructions);
562 remainingDistance = polDistance;
563 remainingStepDistance = instructions[instructionIndex].distanceValue;
564 routeDuration = route.duration.value;
565 remainingTime = routeDuration;
566 averageSpeed = (route.distance.value / routeDuration);
568 /* jshint camelcase: false */
569 $("#destinationAddress").html(destination.formatted_address);
570 $("#destinationAddressTown").html(destination.address_components[2].short_name + ", " + destination.address_components[3].short_name);
571 /* jshint camelcase: true */
573 var averageSpeedText;
575 if (useMetricSystem === true) {
576 averageSpeedText = Math.round(averageSpeed * 3.6) + "<span class='fontSizeXXSmall'> km/h</span>" + ")";
578 averageSpeedText = Math.round(averageSpeed * 2.2369362920544) + "<span class='fontSizeXXSmall'> MPH</span>" + ")";
581 $("#arrivalText").html("ARRIVAL " + addSecondsToCurrentTime(routeDuration) + " (" + averageSpeedText);
583 updateNavigationPanel();
584 window.setTimeout(function(){
586 }, 2000); // Allow time for the initial map display
590 * Method renders the route.
591 * @method renderRoute
592 * @for NavigationGoogle
593 * @param origin {Object} Origin position object.
594 * @param destination {Object} Destination position object.
596 function renderRoute(origin, destination) {
598 if (directionsService === null) {
599 directionsService = new google.maps.DirectionsService();
602 if (directionRenderer === null) {
603 directionRenderer = new google.maps.DirectionsRenderer();
606 directionRenderer.setMap(map);
610 destination: destination,
611 travelMode: google.maps.DirectionsTravelMode.DRIVING
614 directionsService.route(request, function (response, status) {
616 console.log(response);
617 if (status === google.maps.DirectionsStatus.OK && response.routes.length) {
618 if (response.routes && response.routes[0] && response.routes[0].legs && response.routes[0].legs[0]) {
619 polyline = new google.maps.Polyline({
623 route = response.routes[0].legs[0];
624 directionRenderer.setDirections(response);
626 var steps = route.steps;
627 var distanceStep = 0;
629 for (j = 0; j < steps.length; j++) {
630 var latLngs = steps[j].path;
631 distanceStep = distanceStep + (steps[j].distance.value || 0);
634 distance: (steps[j].distance || 0),
635 distanceValue: distanceStep,
636 instruction: (steps[j].instructions ? steps[j].instructions.trim() : "")
639 instructions.push(instruction);
641 for (k = 0; k < latLngs.length; k++) {
642 polyline.getPath().push(latLngs[k]);
648 console.log('Route calculation failed: ' + status);
654 * Starts the navigation.
655 * @method startNavigation
656 * @for NavigationGoogle
658 function startNavigation(){
661 if (geocoder == null) {
662 geocoder = new google.maps.Geocoder();
665 address: originAddress
666 }, function (results, status) {
667 if (status === "OK" && results.length) {
668 origin = results[0] || null;
669 map.setCenter(origin.geometry.location);
671 address: destinationAddress
672 }, function (results, status) {
673 if (status === "OK" && results.length) {
674 destination = results[0] || null;
676 if (origin && destination) {
677 renderRoute(origin.geometry.location, destination.geometry.location);
680 console.log("Destination not found. Unable to get the route.");
684 console.log("Origin not found. Unable to get the route.");
688 console.log(error.message);
692 $(document).ready(function () {
694 /* global Bootstrap*/
695 bootstrap = new Bootstrap(function (status) {
696 $("#topBarIcons").topBarIconsPlugin('init', 'navigation');
697 $("#upNextRectangle").boxCaptionPlugin('init', 'up next');
698 $("#destinationProgress").progressBarPlugin('init', 'progressBar');
699 $("#destinationRectangle").boxCaptionPlugin('init', 'destination');
700 $("#stillToGoRectangle").boxCaptionPlugin('init', 'still to go');
701 $('#bottomPanel').bottomPanel('init');
704 backgroundColor: "transparent",
705 mapTypeId: google.maps.MapTypeId.ROADMAP,
706 mapTypeControl: false,
707 streetViewControl: false,
711 map = new google.maps.Map(document.getElementById("map_div"), options);
715 $("#placesLibrary").library("setSectionTitle", "PLACES LIBRARY");
716 $("#placesLibrary").library("init");
717 $("#placesLibrary").library("hideAlphabet");
718 $("#placesLibrary").library("setGridBtnDisabled", true);
719 $("#placesLibrary").library("setSearchBtnDisabled", true);
721 $("#placesButton").on("click", function() {
722 $("#placesLibrary").library("showPage");
725 var tabMenuItems = [ {
726 text : "DESTINATIONS",
734 $("#placesLibrary").library("tabMenuTemplateCompile", tabMenuModel);
735 $("#placesLibrary").library("setContentDelegate", "templates/placesListDelegate.html");
736 $("#placesLibrary").library("contentTemplateCompile", places, "placesLibraryContentList");
741 * Restarts the navigation with a new destination address taken into account.
742 * @method changeDestinationAddress
743 * @for NavigationGoogle
744 * @param newDestinationAddress {String} a new destination address
746 function changeDestinationAddress(newDestinationAddress) {
748 $("#placesLibrary").library("hidePage");
750 destinationAddress = newDestinationAddress;
758 remainingDistance = 0;
759 remainingStepDistance = 0;
765 instructionIndex = 0;
766 instructionChanged = true;
771 clearTimeout(timerHandle);
774 directionRenderer.setMap(null);
775 directionRenderer = null;