Start work on second engine
[rails.git] / app / assets / javascripts / routing.js.erb
1 /*
2         osm.org routing interface
3         
4         See also:
5         https://github.com/apmon/openstreetmap-website/tree/routing2
6         https://github.com/apmon/openstreetmap-website/compare/routing2
7         https://github.com/apmon/openstreetmap-website/blob/9755c3ae0a8d0684d43760f91dc864ff42d8477a/app/views/routing/start.js.erb
8
9         *** draggable start/end markers
10         *** click each part
11         *** translation (including all alerts and presentation)
12         *** export GPX
13         *** URL history (or do we consciously not want to support that?)
14 */
15
16 var TURN_INSTRUCTIONS=["",
17         "Continue on ",                         // 1
18         "Slight right onto ",           // 2
19         "Turn right onto ",                     // 3
20         "Sharp right onto ",            // 4
21         "U-turn along ",                        // 5
22         "Sharp left onto ",                     // 6
23         "Turn left onto ",                      // 7
24         "Slight left onto ",            // 8
25         "(via point) ",                         // 9
26         "Follow ",                                      // 10
27         "At roundabout take ",          // 11
28         "Leave roundabout - ",          // 12
29         "Stay on roundabout - ",        // 13
30         "Start at end of ",                     // 14
31         "Reach destination",            // 15
32         "Go against one-way on ",       // 16
33         "End of one-way on "]           // 17
34
35 var ROUTING_POLYLINE={
36         color: '#03f',
37         opacity: 0.3,
38         weight: 10
39 };
40
41
42 OSM.Routing=function(map,name,jqSearch) {
43         var r={};
44         r.map=map;                              // Leaflet map
45         r.name=name;                    // global variable name of this instance (needed for JSONP)
46         r.jqSearch=jqSearch;    // JQuery object for search panel
47
48         r.route_from=null;
49         r.route_to=null;
50         r.viaPoints=[];
51         r.polyline=null;
52         r.chosenEngine=null;
53
54         // Geocoding
55
56         r.geocode=function(id,event) { var _this=this;
57                 var field=event.target;
58                 var v=event.target.value;
59                 // *** do something if v==''
60                 var querystring = '<%= NOMINATIM_URL %>search?q=' + encodeURIComponent(v) + '&format=json';
61                 // *** &accept-language=<%#= request.user_preferred_languages.join(',') %>
62                 // *** prefer current viewport
63                 $.getJSON(querystring, function(json) { _this._gotGeocode(json,field); });
64         };
65         
66         r._gotGeocode=function(json,field) {
67                 if (json.length==0) {
68                         alert("Sorry, couldn't find that place.");      // *** internationalise
69                         r[field.id]=null;
70                         return;
71                 }
72                 field.value=json[0].display_name;
73                 var lat=Number(json[0].lat), lon=Number(json[0].lon);
74                 r[field.id]=[lat,lon];
75                 // ** update markers
76         };
77         
78         // Route-fetching UI
79
80         r.selectEngine=function(e) {
81                 r.chosenEngine=r.engines[e.target.selectedIndex];
82         };
83         r.requestRoute=function() {
84                 if (r.route_from && r.route_to) {
85                         r.chosenEngine.getRoute(true,[r.route_from,r.route_to]);
86                         // then, when the route has been fetched, it'll call the engine's gotRoute function
87                 }
88         };
89
90         // Take an array of Leaflet LatLngs and draw it as a polyline
91         r.setPolyline=function(line) {
92                 if (r.polyline) map.removeLayer(r.polyline);
93                 r.polyline=L.polyline(line, ROUTING_POLYLINE).addTo(r.map);
94                 r.map.fitBounds(r.polyline.getBounds());
95         };
96
97         // Take an array of directions and write it out
98         // (we use OSRM's route_instructions format)
99         // *** translations?
100         r.setItinerary=function(steps) {
101                 // Create base table
102                 $("#content").removeClass("overlay-sidebar");
103                 $('#sidebar_content').empty();
104                 var html='<h2><a class="geolink" href="#" onclick="$(~.close_directions~).click();return false;"><span class="icon close"></span></a>Directions</h2>'.replace(/~/g,"'");
105                 html+="<table id='turnbyturn' />";
106                 $('#sidebar_content').html(html);
107                 // Add each row
108                 var cumulative=0;
109                 for (var i=0; i<steps.length; i++) {
110                         var step=steps[i];
111                         var instCodes=step[0].split('-');
112                         // Assemble instruction text
113                         var instText="<b>"+(i+1)+".</b> ";
114                         instText+=TURN_INSTRUCTIONS[instCodes[0]];
115                         if (instCodes[1]) { instText+="exit "+instCodes[1]+" "; }
116                         if (instCodes[0]!=15) { instText+=step[1] ? "<b>"+step[1]+"</b>" : "(unnamed)"; }
117                         // Distance
118                         var dist=step[2];
119                         if (dist<5) { dist=""; }
120                         else if (dist<200) { dist=Math.round(dist/10)*10+"m"; }
121                         else if (dist<1500) { dist=Math.round(dist/100)*100+"m"; }
122                         else if (dist<5000) { dist=Math.round(dist/100)/10+"km"; }
123                         else { dist=Math.round(dist/1000)+"km"; }
124                         // Add to table
125                         var row=$("<tr class='turn'/>");
126                         row.append("<td class='direction i"+instCodes[0]+"'> ");
127                         row.append("<td class='instruction'>"+instText);
128                         row.append("<td class='distance'>"+dist);
129                         with ({num: i, dist: step[3]}) {
130                                 row.on('click',function(e) { 
131                                         r.clickTurn(num, r.polyline.getLatLngs()[dist]);
132                                 });
133                         };
134                         $('#turnbyturn').append(row);
135                         cumulative+=step[2];
136                 }
137         };
138         r.clickTurn=function(num,latlng) {
139                 L.popup().setLatLng(latlng).setContent("<p>"+(num+1)+"</p>").openOn(r.map);
140         };
141
142
143         // Close all routing UI
144         
145         r.close=function() {
146                 $("#content").addClass("overlay-sidebar");
147                 if (r.polyline) map.removeLayer(r.polyline);
148         };
149
150         // Add engines
151         
152         r.engines=[];
153         r.addEngine=function(engine) {
154                 // Save engine
155                 var i=r.engines.length;
156                 engine.subscript=i;
157                 r['engine'+i]=engine;
158                 r.engines.push(engine);
159
160                 // Add generic JSONP function
161                 engine.requestJSONP=function(url) {
162                         var script = document.createElement('script');
163                         script.src = url+r.name+".engine"+this.subscript+".gotRoute";
164                         // OSRM doesn't like non-alphanumeric, otherwise we could just do OSM.routing.engines["+engine.subscript+"].gotRoute
165                         document.body.appendChild(script); 
166                 };
167
168                 // Populate dropdown
169                 var select=jqSearch.find('select.routing_engines');
170                 select.append("<option value='"+i+"'>"+engine.name+"</option>");
171         };
172
173         // OSRM car engine
174         // *** this should all be shared from an OSRM library somewhere
175         // *** need to clear hints at some point
176
177         r.addEngine({
178                 name: 'Car (OSRM)',
179                 draggable: true,
180                 _hints: {},
181                 getRoute: function(final,points) {
182                         var url="http://router.project-osrm.org/viaroute?z=14&output=json";
183                         for (var i=0; i<points.length; i++) {
184                                 var pair=points[i].join(',');
185                                 url+="&loc="+pair;
186                                 if (this._hints[pair]) url+= "&hint="+this._hints[pair];
187                         }
188                         if (final) url+="&instructions=true";
189                         this.requestJSONP(url+"&jsonp=");
190                 },
191                 gotRoute: function(data) {
192                         if (data.status==207) {
193                                 alert("Couldn't find route between those two places");
194                                 return false;
195                         }
196                         // *** store hints
197                         var line=L.PolylineUtil.decode(data.route_geometry);
198                         for (i=0; i<line.length; i++) { line[i].lat/=10; line[i].lng/=10; }
199                         r.setPolyline(line);
200                         r.setItinerary(data.route_instructions);
201                 }
202         });
203
204         // CloudMade foot engine
205         // *** again, this should be shared from a Cloudmade library somewhere
206         // *** this API key is taken from some example code, not for real live use!
207         
208         r.addEngine({
209                 name: 'Foot (CloudMade)',
210                 draggable: true,
211                 getRoute: function(final,points) {
212                         var url="http://routes.cloudmade.com/8ee2a50541944fb9bcedded5165f09d9/api/0.3/";
213                         var p=[];
214                         for (var i=0; i<points.length; i++) {
215                                 p.push(points[i][0]);
216                                 p.push(points[i][1]);
217                         }
218                         url+=p.join(',');
219                         url+="/foot.js";
220                         this.requestJSONP(url+"?callback=");
221                 },
222                 gotRoute: function(data) {
223                         console.log(data);
224                         // *** todo
225                         // *** will require some degree of refactoring because instruction text is pre-assembled
226                         // *** otherwise largely like OSRM (funny that)
227                 }
228         });
229         
230         // *** add MapQuest
231         // *** add YOURS
232         // *** move into separate files
233         
234         r.chosenEngine=r.engines[0];
235         return r;
236 };