/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU AFFERO General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA or see http://www.gnu.org/licenses/agpl.txt. */ // OSRM routing routines // [management of routing/direction requests and processing of responses] // [TODO: major refactoring scheduled] // some variables OSRM.GLOBALS.route = null; OSRM.GLOBALS.markers = null; OSRM.CONSTANTS.NO_DESCRIPTION = 0; OSRM.CONSTANTS.FULL_DESCRIPTION = 1; OSRM.G.dragging = null; OSRM.GLOBALS.dragid = null; OSRM.GLOBALS.pending = false; OSRM.GLOBALS.pendingTimer = null; OSRM.Routing = { // init routing data structures init: function() { OSRM.G.route = new OSRM.Route(); OSRM.G.markers = new OSRM.Markers(); }, // -- JSONP processing -- // process JSONP response of routing server timeoutRouteSimple: function() { OSRM.Routing.showNoRouteGeometry(); OSRM.Routing.showNoRouteDescription(); document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("TIMED_OUT")+".<p>"; }, timeoutRoute: function() { OSRM.Routing.showNoRouteGeometry(); OSRM.G.route.hideUnnamedRoute(); OSRM.Routing.showNoRouteDescription(); document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("TIMED_OUT")+".<p>"; }, showRouteSimple: function(response) { if(!response) return; if( !OSRM.G.dragging ) // prevent simple routing when no longer dragging return; if( response.status == 207) { OSRM.Routing.showNoRouteGeometry(); OSRM.Routing.showNoRouteDescription(); document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("YOUR_ROUTE_IS_BEING_COMPUTED")+".<p>"; } else { OSRM.Routing.showRouteGeometry(response); OSRM.Routing.showRouteDescriptionSimple(response); } OSRM.Routing.updateHints(response); // // TODO: hack to process final drag event, if it was fenced, but we are still dragging (alternative approach) // if(OSRM.G.pending) { // clearTimeout(OSRM.G.pendingTimer); // OSRM.G.pendingTimer = setTimeout(OSRM.Routing.timeoutDrag,100); // } }, showRoute: function(response) { if(!response) return; if(response.status == 207) { OSRM.Routing.showNoRouteGeometry(); OSRM.G.route.hideUnnamedRoute(); OSRM.Routing.showNoRouteDescription(); document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("NO_ROUTE_FOUND")+".<p>"; } else { OSRM.Routing.showRouteGeometry(response); OSRM.Routing.showRouteNonames(response); OSRM.Routing.showRouteDescription(response); OSRM.Routing.snapRoute(); } OSRM.Routing.updateHints(response); }, // show route geometry showNoRouteGeometry: function() { var positions = []; for(var i=0; i<OSRM.G.markers.route.length;i++) positions.push( OSRM.G.markers.route[i].getPosition() ); OSRM.G.route.showRoute(positions, OSRM.Route.NOROUTE); }, showRouteGeometry: function(response) { OSRM.G.via_points = response.via_points.slice(0); var geometry = OSRM.Routing.decodeRouteGeometry(response.route_geometry, 5); var points = []; for( var i=0; i < geometry.length; i++) { points.push( new L.LatLng(geometry[i][0], geometry[i][1]) ); } OSRM.G.route.showRoute(points, OSRM.Route.ROUTE); }, // route description display (and helper functions) onClickRouteDescription: function(geometry_index) { var positions = OSRM.G.route.getPositions(); OSRM.G.markers.highlight.setPosition( positions[geometry_index] ); OSRM.G.markers.highlight.show(); OSRM.G.markers.highlight.centerView(OSRM.DEFAULTS.HIGHLIGHT_ZOOM_LEVEL); }, onClickCreateShortcut: function(src){ src += '&z='+ OSRM.G.map.getZoom() + '¢er=' + OSRM.G.map.getCenter().lat + ',' + OSRM.G.map.getCenter().lng; OSRM.JSONP.call(OSRM.DEFAULTS.HOST_SHORTENER_URL+src, OSRM.Routing.showRouteLink, OSRM.Routing.showRouteLink_TimeOut, 2000, 'shortener'); document.getElementById('route-prelink').innerHTML = '['+OSRM.loc("GENERATE_LINK_TO_ROUTE")+']'; }, showRouteLink: function(response){ document.getElementById('route-prelink').innerHTML = '[<a id="gpx-link" class = "text-selectable" href="' +response.ShortURL+ '">'+response.ShortURL+'</a>]'; }, showRouteLink_TimeOut: function(){ document.getElementById('route-prelink').innerHTML = '['+OSRM.loc("LINK_TO_ROUTE_TIMEOUT")+']'; }, showRouteDescription: function(response) { // compute query string var query_string = '?rebuild=1'; for(var i=0; i<OSRM.G.markers.route.length; i++) query_string += '&loc=' + OSRM.G.markers.route[i].getLat() + ',' + OSRM.G.markers.route[i].getLng(); // create link to the route var route_link ='<span class="route-summary" id="route-prelink">[<a id="gpx-link" onclick="OSRM.Routing.onClickCreateShortcut(\'' + OSRM.DEFAULTS.WEBSITE_URL + query_string + '\')">'+OSRM.loc("GET_LINK_TO_ROUTE")+'</a>]</span>'; // create GPX link var gpx_link = '<span class="route-summary">[<a id="gpx-link" onClick="document.location.href=\'' + OSRM.DEFAULTS.HOST_ROUTING_URL + query_string + '&output=gpx\';">'+OSRM.loc("GPX_FILE")+'</a>]</span>'; // create route description var route_desc = ""; route_desc += '<table class="results-table">'; for(var i=0; i < response.route_instructions.length; i++){ //odd or even ? var rowstyle='results-odd'; if(i%2==0) { rowstyle='results-even'; } route_desc += '<tr class="'+rowstyle+'">'; route_desc += '<td class="result-directions">'; route_desc += '<img width="18px" src="images/'+OSRM.Routing.getDirectionIcon(response.route_instructions[i][0])+'" alt="" />'; route_desc += "</td>"; route_desc += '<td class="result-items">'; route_desc += '<span class="result-item" onclick="OSRM.Routing.onClickRouteDescription('+response.route_instructions[i][3]+')">'; route_desc += response.route_instructions[i][0]; if( i == 0 ) route_desc += ' ' + OSRM.loc( response.route_instructions[i][6] ); if( response.route_instructions[i][1] != "" ) { route_desc += ' on '; route_desc += '<b>' + response.route_instructions[i][1] + '</b>'; } //route_desc += ' for '; route_desc += '</span>'; route_desc += "</td>"; route_desc += '<td class="result-distance">'; if( i != response.route_instructions.length-1 ) route_desc += '<b>'+OSRM.Utils.metersToDistance(response.route_instructions[i][2])+'</b>'; route_desc += "</td>"; route_desc += "</tr>"; } route_desc += '</table>'; headline = ""; headline += OSRM.loc("ROUTE_DESCRIPTION")+":<br>"; headline += '<div style="float:left;width:40%">'; headline += "<span class='route-summary'>" + OSRM.loc("DISTANCE")+": " + OSRM.Utils.metersToDistance(response.route_summary.total_distance) + "<br>" + OSRM.loc("DURATION")+": " + OSRM.Utils.secondsToTime(response.route_summary.total_time) + "</span>"; headline += '</div>'; headline += '<div style="float:left;text-align:right;width:60%;">'+route_link+'<br>'+gpx_link+'</div>'; var output = ""; output += route_desc; document.getElementById('information-box-headline').innerHTML = headline; document.getElementById('information-box').innerHTML = output; }, showRouteDescriptionSimple: function(response) { headline = OSRM.loc("ROUTE_DESCRIPTION")+":<br>"; headline += "<span class='route-summary'>" + OSRM.loc("DISTANCE")+": " + OSRM.Utils.metersToDistance(response.route_summary.total_distance) + "<br>" + OSRM.loc("DURATION")+": " + OSRM.Utils.secondsToTime(response.route_summary.total_time) + "</span>"; headline += '<br><br>'; document.getElementById('information-box-headline').innerHTML = headline; document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("YOUR_ROUTE_IS_BEING_COMPUTED")+".<p>"; }, showNoRouteDescription: function() { headline = OSRM.loc("ROUTE_DESCRIPTION")+":<br>"; headline += "<span class='route-summary'>" + OSRM.loc("DISTANCE")+": N/A" + "<br>" + OSRM.loc("DURATION")+": N/A" + "</span>"; headline += '<br><br>'; document.getElementById('information-box-headline').innerHTML = headline; document.getElementById('information-box').innerHTML = "<br><p style='font-size:14px;font-weight:bold;text-align:center;'>"+OSRM.loc("YOUR_ROUTE_IS_BEING_COMPUTED")+".<p>"; }, // unnamed streets display showRouteNonames: function(response) { // do not display unnamed streets? if( document.getElementById('option-highlight-nonames').checked == false) { OSRM.G.route.hideUnnamedRoute(); return; } // mark geometry positions where unnamed/named streets switch var named = []; for (var i = 0; i < response.route_instructions.length; i++) { if( response.route_instructions[i][1] == '' ) named[ response.route_instructions[i][3] ] = false; // no street name else named[ response.route_instructions[i][3] ] = true; // yes street name } // aggregate geometry for unnamed streets var geometry = OSRM.Routing.decodeRouteGeometry(response.route_geometry, 5); var is_named = true; var current_positions = []; var all_positions = []; for( var i=0; i < geometry.length; i++) { current_positions.push( new L.LatLng(geometry[i][0], geometry[i][1]) ); // still named/unnamed? if( (named[i] == is_named || named[i] == undefined) && i != geometry.length-1 ) continue; // switch between named/unnamed! if(is_named == false) all_positions.push( current_positions ); current_positions = []; current_positions.push( new L.LatLng(geometry[i][0], geometry[i][1]) ); is_named = named[i]; } // display unnamed streets OSRM.G.route.showUnnamedRoute(all_positions); }, //-- main function -- // generate server calls to query routes getRoute: function(do_description) { // if source or target are not set -> hide route if( OSRM.G.markers.route.length < 2 ) { OSRM.G.route.hideRoute(); return; } // prepare JSONP call var type = null; var callback = null; var timeout = null; var source = OSRM.DEFAULTS.HOST_ROUTING_URL; source += '?z=' + OSRM.G.map.getZoom() + '&output=json' + '&geomformat=cmp'; if(OSRM.G.markers.checksum) source += '&checksum=' + OSRM.G.markers.checksum; for(var i=0; i<OSRM.G.markers.route.length; i++) { source += '&loc=' + OSRM.G.markers.route[i].getLat() + ',' + OSRM.G.markers.route[i].getLng(); if( OSRM.G.markers.route[i].hint) source += '&hint=' + OSRM.G.markers.route[i].hint; } // decide whether it is a dragging call or a normal one if (do_description) { callback = OSRM.Routing.showRoute; timeout = OSRM.Routing.timeoutRoute; source +='&instructions=true'; type = 'route'; } else { callback = OSRM.Routing.showRouteSimple; timeout = OSRM.Routing.timeoutRouteSimple; source +='&instructions=false'; type = 'dragging'; } // do call var called = OSRM.JSONP.call(source, callback, timeout, OSRM.DEFAULTS.JSONP_TIMEOUT, type); // TODO: hack to process final drag event, if it was fenced, but we are still dragging if(called == false && !do_description) { clearTimeout(OSRM.G.pendingTimer); OSRM.G.pendingTimer = setTimeout(OSRM.Routing.timeoutDrag,OSRM.DEFAULTS.JSONP_TIMEOUT); } else { clearTimeout(OSRM.G.pendingTimer); } // // TODO: hack to process final drag event, if it was fenced, but we are still dragging (alternative approach) // if(called == false && !do_description) { // OSRM.G.pending = true; // } else { // clearTimeout(OSRM.G.pendingTimer); // OSRM.G.pending = false; // } }, timeoutDrag: function() { OSRM.G.markers.route[OSRM.G.dragid].hint = null; OSRM.Routing.getRoute(OSRM.C.NO_DESCRIPTION); }, //-- helper functions -- //decode compressed route geometry decodeRouteGeometry: function(encoded, precision) { precision = Math.pow(10, -precision); var len = encoded.length, index=0, lat=0, lng = 0, array = []; while (index < len) { var b, shift = 0, result = 0; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); var dlat = ((result & 1) ? ~(result >> 1) : (result >> 1)); lat += dlat; shift = 0; result = 0; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); var dlng = ((result & 1) ? ~(result >> 1) : (result >> 1)); lng += dlng; array.push([lat * precision, lng * precision]); } return array; }, // update hints of all markers updateHints: function(response) { var hint_locations = response.hint_data.locations; OSRM.G.markers.checksum = response.hint_data.checksum; for(var i=0; i<hint_locations.length; i++) OSRM.G.markers.route[i].hint = hint_locations[i]; }, // snap all markers to the received route snapRoute: function() { var positions = OSRM.G.route.getPositions(); OSRM.G.markers.route[0].setPosition( positions[0] ); OSRM.G.markers.route[OSRM.G.markers.route.length-1].setPosition( positions[positions.length-1] ); for(var i=0; i<OSRM.G.via_points.length; i++) OSRM.G.markers.route[i+1].setPosition( new L.LatLng(OSRM.G.via_points[i][0], OSRM.G.via_points[i][1]) ); OSRM.Geocoder.updateAddress(OSRM.C.SOURCE_LABEL); OSRM.Geocoder.updateAddress(OSRM.C.TARGET_LABEL); }, // map driving instructions to icons // [TODO: better implementation, language-safe] getDirectionIcon: function(name) { var directions = { "Turn left":"turn-left.png", "Turn right":"turn-right.png", "U-Turn":"u-turn.png", "Head":"continue.png", "Continue":"continue.png", "Turn slight left":"slight-left.png", "Turn slight right":"slight-right.png", "Turn sharp left":"sharp-left.png", "Turn sharp right":"sharp-right.png", "Enter roundabout and leave at first exit":"round-about.png", "Enter roundabout and leave at second exit":"round-about.png", "Enter roundabout and leave at third exit":"round-about.png", "Enter roundabout and leave at fourth exit":"round-about.png", "Enter roundabout and leave at fifth exit":"round-about.png", "Enter roundabout and leave at sixth exit":"round-about.png", "Enter roundabout and leave at seventh exit":"round-about.png", "Enter roundabout and leave at eighth exit":"round-about.png", "Enter roundabout and leave at nineth exit":"round-about.png", "Enter roundabout and leave at tenth exit":"round-about.png", "Enter roundabout and leave at one of the too many exit":"round-about.png", "You have reached your destination":"target.png" }; if( directions[name] ) return directions[name]; else return "default.png"; }, // -- gui functions -- // click: button "reset" resetRouting: function() { document.getElementById('input-source-name').value = ""; document.getElementById('input-target-name').value = ""; OSRM.G.route.hideAll(); OSRM.G.markers.removeAll(); OSRM.G.markers.highlight.hide(); document.getElementById('information-box').innerHTML = ""; document.getElementById('information-box-headline').innerHTML = ""; OSRM.JSONP.reset(); }, // click: button "reverse" reverseRouting: function() { // invert input boxes var tmp = document.getElementById("input-source-name").value; document.getElementById("input-source-name").value = document.getElementById("input-target-name").value; document.getElementById("input-target-name").value = tmp; // invert route OSRM.G.markers.route.reverse(); if(OSRM.G.markers.route.length == 1) { if(OSRM.G.markers.route[0].label == OSRM.C.TARGET_LABEL) { OSRM.G.markers.route[0].label = OSRM.C.SOURCE_LABEL; OSRM.G.markers.route[0].marker.setIcon( new L.Icon('images/marker-source.png') ); } else if(OSRM.G.markers.route[0].label == OSRM.C.SOURCE_LABEL) { OSRM.G.markers.route[0].label = OSRM.C.TARGET_LABEL; OSRM.G.markers.route[0].marker.setIcon( new L.Icon('images/marker-target.png') ); } } else if(OSRM.G.markers.route.length > 1){ OSRM.G.markers.route[0].label = OSRM.C.SOURCE_LABEL; OSRM.G.markers.route[0].marker.setIcon( new L.Icon('images/marker-source.png') ); OSRM.G.markers.route[OSRM.G.markers.route.length-1].label = OSRM.C.TARGET_LABEL; OSRM.G.markers.route[OSRM.G.markers.route.length-1].marker.setIcon( new L.Icon('images/marker-target.png') ); } // recompute route if( OSRM.G.route.isShown() ) { OSRM.Routing.getRoute(OSRM.C.FULL_DESCRIPTION); OSRM.G.markers.highlight.hide(); } else { document.getElementById('information-box').innerHTML = ""; document.getElementById('information-box-headline').innerHTML = ""; } }, // click: button "show" showMarker: function(marker_id) { if( OSRM.JSONP.fences["geocoder_source"] || OSRM.JSONP.fences["geocoder_target"] ) return; if( marker_id == OSRM.C.SOURCE_LABEL && OSRM.G.markers.hasSource() ) OSRM.G.markers.route[0].centerView(); else if( marker_id == OSRM.C.TARGET_LABEL && OSRM.G.markers.hasTarget() ) OSRM.G.markers.route[OSRM.G.markers.route.length-1].centerView(); }, // changed: any inputbox (is called when return is pressed [after] or focus is lost [before]) inputChanged: function(marker_id) { if( marker_id == OSRM.C.SOURCE_LABEL) OSRM.Geocoder.call(OSRM.C.SOURCE_LABEL, document.getElementById('input-source-name').value); else if( marker_id == OSRM.C.TARGET_LABEL) OSRM.Geocoder.call(OSRM.C.TARGET_LABEL, document.getElementById('input-target-name').value); } };