-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.html
257 lines (234 loc) · 9.67 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
<!DOCTYPE html>
<html>
<head>
<title>Boston Hubway Challenge - Travelling Salesman Optimal Route</title>
<link rel="image_src" href="route.png"/>
<meta property="og:image" content="route.png" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width, minimum-scale=1.0, maximum-scale=1.0" />
<style type="text/css">
html { height: 100%; font: 13px Helvetica,arial,freesans,clean,sans-serif; }
body { height: 100%; margin: 0; padding: 0 }
.hidden { display: none;}
#bottom_button_panel { text-align: center;}
#map_canvas { float:left;width:70%; height:100%; }
#right_panel {float:right;width:29%;height 100%; padding: 0.5%;}
@media screen and (max-device-width: 480px){
#map_canvas { float:none; width:100%; height:70% }
#right_panel { float:none; width:96%; padding: 3% }
}
</style>
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?&sensor=false">
</script>
<script src="http://platform.twitter.com/widgets.js"></script>
<script type="text/javascript">
var map;
var route;
var route_length;
var directionsDisplay;
var flightPath;
var markers=[];
var directionsService = new google.maps.DirectionsService();
var bicyclingLayer = new google.maps.BicyclingLayer();
var displayedLeg = -1;
function initialize() {
var boston = new google.maps.LatLng(42.340592,-71.09489);
var myMapOptions = {
center: boston,
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"), myMapOptions);
var myRendererOptions = {
suppressMarkers: true,
suppressInfoWindows: false,
suppressBicyclingLayer: true,
};
directionsDisplay = new google.maps.DirectionsRenderer(myRendererOptions);
directionsDisplay.setMap(map);
directionsDisplay.setPanel(document.getElementById("directionsPanel"));
var myPolyLineOptions = {
strokeColor: "#FF0000",
strokeOpacity: 0.5,
strokeWeight: 3,
}
flightPath = new google.maps.Polyline(myPolyLineOptions);
flightPath.setMap(map);
load_route();
}
function show_bike_layer(){
bicyclingLayer.setMap(map);
}
function hide_bike_layer(){
bicyclingLayer.setMap(null);
}
function check_bike_layer(){
if(document.getElementById('bike_checkbox').checked) { show_bike_layer() }
else { hide_bike_layer() }
}
function show_next_buttons(){
document.getElementById("next_button_1").className = 'next_button';
document.getElementById("next_button_2").className = 'next_button';
}
function next(i){
// returns i+1, or 0 if that would be off the end of the route
return (i*1+1 < route_length) ? (i+1) : 0;
}
function get_coords(i){
return new google.maps.LatLng(route[i].lat, route[i].long)
//return ""+route[i].lat + ',' + route[i].long;
}
function update_leg_info(i,directionResult){
var myRoute = directionResult.routes[0].legs[0];
var duration = myRoute.duration.text
var container = document.getElementById("nextPanel");
container.innerHTML="This is leg "+(i*1+1)+" of "+route_length+" (~"+duration+")";
document.getElementById("bottom_button_panel").style="display:block;";
}
function calc_next_leg() {
calc_route_leg(next(displayedLeg));
}
function show_route_leg(i, directionResult) {
directionsDisplay.setDirections(directionResult);
// unset any set icons, then set the start and end icons
for (var j = 0; j<markers.length; j++){
if (markers[j].getIcon()) { markers[j].setIcon() ;}
}
markers[i].setIcon('http://maps.gstatic.com/mapfiles/markers2/icon_greenA.png');
markers[next(i)].setIcon('http://maps.gstatic.com/mapfiles/markers2/icon_greenB.png');
var overview_path = directionResult.routes[0].overview_path;
var start = get_coords(i);
var index_of_start = -1;
var path = flightPath.getPath();
path.forEach(function(element,index){
if (element.equals(start)) { index_of_start = index; }
});
if (index_of_start<0) {
// can't find the coarse start point, probably already updated this leg
// or loaded the detailed_route
return;
}
path.removeAt(index_of_start);
for (var j = 0; j<overview_path.length; j++){
path.insertAt(index_of_start+j, overview_path[j]);
}
}
function calc_route_leg(i){
if (displayedLeg < 0) { show_next_buttons() }
if (i == route.length) { i = 0 ;}
var start = get_coords(i);
var end = get_coords(next(i));
var request = {
origin: start,
destination: end,
travelMode: google.maps.TravelMode.BICYCLING,
};
directionsService.route(request, function(result, status) {
if (status == google.maps.DirectionsStatus.OK) {
displayedLeg = i;
show_route_leg(i, result);
update_leg_info(i, result);
}
else {
document.getElementById("directionsPanel").innerHTML="Error fetching directions";
}
});
}
function plot_route(route){
var flightPlanCoordinates = [];
for (var i = 0; i < route.length; i++){
station = route[i];
// Create a Google LatLng object to pass to the Google Marker
var point = new google.maps.LatLng(station.lat, station.long);
// save it in the polyline coordinates list
flightPlanCoordinates[i] = point;
// Create the Google Marker Point with the LatLng object
var marker = new google.maps.Marker({
position: point,
map: map,
title: station.name,
id: i*1,
});
// Create an Event Listener that does something when user clicks a station
google.maps.event.addListener(marker, 'click',
function(){
calc_route_leg(this.id);
});
markers[i] = marker;
}
// close the loop on the flight plan
flightPlanCoordinates[i] = flightPlanCoordinates[0];
// draw the flight plan
flightPath.setPath(flightPlanCoordinates);
}
function load_route() {
var xobj = new XMLHttpRequest();
if (xobj.overrideMimeType) xobj.overrideMimeType("application/json");
xobj.open('GET', 'route.json', true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4) {
var jsonTexto = xobj.responseText;
route = JSON.parse(jsonTexto);
route_length = route.length
plot_route(route);
load_detailed_route();
}
}
xobj.send(null);
}
function plot_detailed_route(detailed_route) {
// clear the fligtPath red line and replace with the detailed_route
var path = flightPath.getPath();
path.clear();
for (var i = 0; i<detailed_route.length; i++) {
var corner = new google.maps.LatLng(detailed_route[i][0],detailed_route[i][1]);
path.push(corner);
}
}
function load_detailed_route() {
// Try to load a detailed route plan from detailed_route.json
var xobj = new XMLHttpRequest();
if (xobj.overrideMimeType) xobj.overrideMimeType("application/json");
xobj.open('GET', 'detailed_route.json', true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4) {
var jsonTexto = xobj.responseText;
detailed_route = JSON.parse(jsonTexto);
plot_detailed_route(detailed_route);
}
}
xobj.send(null);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" ></div>
<div id="right_panel">
Overall Route: 3 hours 21 minutes.
<span style="white-space:nowrap;"><input type='checkbox' onchange='check_bike_layer();' id='bike_checkbox' label='Show bike paths' /> show bike paths.</span>
<span id="nextPanel" style="white-space:nowrap;">
<input type='button' value='Start' onclick='calc_route_leg(0);' />
</span>
<input class='next_button hidden' id='next_button_1' type='button' value='Get Next Leg' onclick='calc_next_leg();' />
<div id="directionsPanel"></div>
<div id="bottom_button_panel">
<input class='next_button hidden' id='next_button_2' type='button' value='Get Next Leg' onclick='calc_next_leg(); window.scrollTo(0,0);' /></div>
<h2>About this project</h2>
<p>
This route connects all<sup>*</sup> the <a href="http://www.thehubway.com/">Hubway</a> bicycle rental stations in Boston,
using the quickest possible cycling route according to cycling directions provided by
<a href="https://developers.google.com/maps/">Google Maps</a>.
Click on a marker on the map for directions from that station, or click the Start button above.
The <a href="http://en.wikipedia.org/wiki/Travelling_salesman_problem">travelling salesman</a>
optimization was performed using Google's <a href="http://code.google.com/p/or-tools/">Operations Research Tools</a>.
All the source code is available, so if you have any suggestions then
<a href="https://github.com/rwest/hubway/">fork me on GitHub</a>.
If you try the route let me know how you get on <a href="https://github.com/rwest/Hubway/issues">here</a>,
or follow me on twitter <a href="http://twitter.com/richardhwest">here</a>.
Anyone fancy a <a href="https://github.com/rwest/Hubway/wiki">race</a>?
</p>
<center><a href="https://twitter.com/intent/tweet?screen_name=richardhwest&text=%40Hubway" class="twitter-mention-button" data-related="richardhwest,Hubway">Tweet to @richardhwest</a></center>
<p><small><sup>*</sup>North Station is excluded, because Google couldn't figure out how to cycle to where Hubway claim the bike rack is.</small></p>
</div>
</body>
</html>