3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | html { height: 100% }
17 | body { height: 100%; margin: 0; padding: 0 }
18 | #map_canvas { height: 100% }
19 |
--------------------------------------------------------------------------------
/src/main/resources/org/onebusaway/gtfs_realtime/visualizer/index.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 | GTFS-realtime Visualizer
22 |
23 |
26 |
29 |
30 |
31 |
32 |
33 | If you can still read this, something went wrong with the Javascript.
34 |
35 |
--------------------------------------------------------------------------------
/src/main/resources/org/onebusaway/gtfs_realtime/visualizer/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 Brian Ferris
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | function Init() {
18 |
19 | var hostandport = window.location.hostname + ':' + window.location.port;
20 |
21 | /**
22 | * Create a custom-styled Google Map with no labels and custom color scheme.
23 | */
24 | var CreateMap = function() {
25 | var map_style = [ {
26 | elementType : "labels",
27 | stylers : [ {
28 | visibility : "off"
29 | } ]
30 | }, {
31 | stylers : [ {
32 | saturation : -69
33 | } ]
34 | } ];
35 | var map_canvas = document.getElementById("map_canvas");
36 | var myOptions = {
37 | center : new google.maps.LatLng(42.349, -71.059),
38 | zoom : 8,
39 | styles : map_style,
40 | mapTypeId : google.maps.MapTypeId.ROADMAP
41 | };
42 | return new google.maps.Map(map_canvas, myOptions);
43 | };
44 |
45 | var map = CreateMap();
46 |
47 | /**
48 | * We want to assign a random color to each bus in our visualization. We
49 | * pick from the HSV color-space since it gives more natural colors.
50 | */
51 | var HsvToRgb = function(h, s, v) {
52 | h_int = parseInt(h * 6);
53 | f = h * 6 - h_int;
54 | var a = v * (1 - s);
55 | var b = v * (1 - f * s);
56 | var c = v * (1 - (1 - f) * s);
57 | switch (h_int) {
58 | case 0:
59 | return [ v, c, a ];
60 | case 1:
61 | return [ b, v, a ];
62 | case 2:
63 | return [ a, v, c ];
64 | case 3:
65 | return [ a, b, v ];
66 | case 4:
67 | return [ c, a, v ];
68 | case 5:
69 | return [ v, a, b ];
70 | }
71 | };
72 |
73 | var HsvToRgbString = function(h, s, v) {
74 | var rgb = HsvToRgb(h, s, v);
75 | for ( var i = 0; i < rgb.length; ++i) {
76 | rgb[i] = parseInt(rgb[i] * 256)
77 | }
78 | return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
79 | };
80 |
81 | var h = Math.random();
82 | var golden_ratio_conjugate = 0.618033988749895;
83 |
84 | var NextRandomColor = function() {
85 | h = (h + golden_ratio_conjugate) % 1;
86 | return HsvToRgbString(h, 0.90, 0.90)
87 | };
88 |
89 | var icon = new google.maps.MarkerImage(
90 | 'http://' + hostandport + '/WhiteCircle8.png', null, null,
91 | new google.maps.Point(4, 4));
92 |
93 | var CreateVehicle = function(v_data) {
94 | var point = new google.maps.LatLng(v_data.lat, v_data.lon);
95 | var path = new google.maps.MVCArray();
96 | path.push(point);
97 | var marker_opts = {
98 | clickable : true,
99 | draggable : false,
100 | flat : false,
101 | icon : icon,
102 | map : map,
103 | position : point,
104 | title : 'id=' + v_data.id
105 | };
106 | var polyline_opts = {
107 | clickable : false,
108 | editable : false,
109 | map : map,
110 | path : path,
111 | strokeColor : NextRandomColor(),
112 | strokeOpacity : 0.8,
113 | strokeWeight : 4
114 | };
115 | return {
116 | id : v_data.id,
117 | marker : new google.maps.Marker(marker_opts),
118 | polyline : new google.maps.Polyline(polyline_opts),
119 | path : path,
120 | lastUpdate : v_data.lastUpdate
121 | };
122 | };
123 |
124 | function CreateVehicleUpdateOperation(vehicle, lat, lon) {
125 | return function() {
126 | var point = new google.maps.LatLng(lat, lon);
127 | vehicle.marker.setPosition(point);
128 | var path = vehicle.path;
129 | var index = path.getLength() - 1;
130 | path.setAt(index, point);
131 | };
132 | };
133 |
134 | var vehicles_by_id = {};
135 | var animation_steps = 20;
136 |
137 | function UpdateVehicle(v_data, updates) {
138 | var id = v_data.id;
139 | if (!(id in vehicles_by_id)) {
140 | vehicles_by_id[id] = CreateVehicle(v_data);
141 | }
142 | var vehicle = vehicles_by_id[id];
143 | if (vehicle.lastUpdate >= v_data.lastUpdate) {
144 | return;
145 | }
146 | vehicle.lastUpdate = v_data.lastUpdate
147 |
148 | var path = vehicle.path;
149 | var last = path.getAt(path.getLength() - 1);
150 | path.push(last);
151 |
152 | var lat_delta = (v_data.lat - last.lat()) / animation_steps;
153 | var lon_delta = (v_data.lon - last.lng()) / animation_steps;
154 |
155 | if (lat_delta != 0 && lon_delta != 0) {
156 | for ( var i = 0; i < animation_steps; ++i) {
157 | var lat = last.lat() + lat_delta * (i + 1);
158 | var lon = last.lng() + lon_delta * (i + 1);
159 | var op = CreateVehicleUpdateOperation(vehicle, lat, lon);
160 | updates[i].push(op);
161 | }
162 | }
163 | };
164 |
165 | var first_update = true;
166 |
167 | var ProcessVehicleData = function(data) {
168 | var vehicles = jQuery.parseJSON(data);
169 | var updates = [];
170 | var bounds = new google.maps.LatLngBounds();
171 | for ( var i = 0; i < animation_steps; ++i) {
172 | updates.push(new Array());
173 | }
174 | jQuery.each(vehicles, function() {
175 | UpdateVehicle(this, updates);
176 | bounds.extend(new google.maps.LatLng(this.lat, this.lon));
177 | });
178 | if (first_update && ! bounds.isEmpty()) {
179 | map.fitBounds(bounds);
180 | first_update = false;
181 | }
182 | var applyUpdates = function() {
183 | if (updates.length == 0) {
184 | return;
185 | }
186 | var fs = updates.shift();
187 | for ( var i = 0; i < fs.length; i++) {
188 | fs[i]();
189 | }
190 | setTimeout(applyUpdates, 1);
191 | };
192 | setTimeout(applyUpdates, 1);
193 | };
194 |
195 | /**
196 | * We create a WebSocket to listen for vehicle position updates from our
197 | * webserver.
198 | */
199 | if ("WebSocket" in window) {
200 | var ws = new WebSocket("ws://" + hostandport + "/data.json");
201 | ws.onopen = function() {
202 | console.log("WebSockets connection opened");
203 | }
204 | ws.onmessage = function(e) {
205 | console.log("Got WebSockets message");
206 | ProcessVehicleData(e.data);
207 | }
208 | ws.onclose = function() {
209 | console.log("WebSockets connection closed");
210 | }
211 | } else {
212 | alert("No WebSockets support");
213 | }
214 | }
--------------------------------------------------------------------------------
/src/main/resources/org/onebusaway/gtfs_realtime/visualizer/usage.txt:
--------------------------------------------------------------------------------
1 | Description:
2 | Visualize GTFS-realtime vehicle position data.
3 |
4 | Usage:
5 | java -jar demo.jar --vehiclePositionsUrl=url
6 |
7 | Args:
8 | --vehiclePositionsUrl=url GTFS-realtime vehicle positions url
9 |
--------------------------------------------------------------------------------