--- a/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/index.html +++ b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/index.html @@ -1,1 +1,737 @@ - +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/> + <meta http-equiv="content-type" content="text/html; charset=utf-8"/> + <title>[agency]</title> + <link href="file/style.css" rel="stylesheet" type="text/css" /> + <style type="text/css"> + v\:* { + behavior:url(#default#VML); + } + </style> + <script src="http://[host]/maps?file=api&v=2&key=[key]" type="text/javascript"></script> + <script src="/file/labeled_marker.js" type="text/javascript"></script> + <script src="/file/calendarpopup.js" type="text/javascript"></script> + <script language="VBScript" src="/file/svgcheck.vbs"></script> + <script type="text/javascript"> + //<![CDATA[ + var map; + // Set to true when debugging for log statements about HTTP requests. + var log = false; + var twelveHourTime = false; // set to true to see AM/PM + var selectedRoute = null; + var forbid_editing = [forbid_editing]; + + function load() { + if (GBrowserIsCompatible()) { + sizeRouteList(); + var map_dom = document.getElementById("map"); + map = new GMap2(map_dom); + map.addControl(new GLargeMapControl()); + map.addControl(new GMapTypeControl()); + map.addControl(new GOverviewMapControl()); + map.enableScrollWheelZoom(); + var bb = new GLatLngBounds(new GLatLng([min_lat], [min_lon]),new GLatLng([max_lat], [max_lon])); + map.setCenter(bb.getCenter(), map.getBoundsZoomLevel(bb)); + map.enableDoubleClickZoom(); + initIcons(); + GEvent.addListener(map, "moveend", callbackMoveEnd); + GEvent.addListener(map, "zoomend", callbackZoomEnd); + callbackMoveEnd(); // Pretend we just moved to current center + fetchRoutes(); + } + } + + function callbackZoomEnd() { + } + + function callbackMoveEnd() { + // Map moved, search for stops near the center + fetchStopsInBounds(map.getBounds()); + } + + /** + * Fetch a sample of stops in the bounding box. + */ + function fetchStopsInBounds(bounds) { + url = "/json/boundboxstops?n=" + bounds.getNorthEast().lat() + + "&e=" + bounds.getNorthEast().lng() + + "&s=" + bounds.getSouthWest().lat() + + "&w=" + bounds.getSouthWest().lng() + + "&limit=50"; + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayStopsBackground); + } + + /** + * Displays stops returned by the server on the map. Expected to be called + * when GDownloadUrl finishes. + * + * @param {String} data JSON encoded list of list, each + * containing a row of stops.txt + * @param {Number} responseCode Response code from server + */ + function callbackDisplayStops(data, responseCode) { + if (responseCode != 200) { + return; + } + clearMap(); + var stops = eval(data); + if (stops.length == 1) { + var marker = addStopMarkerFromList(stops[0], true); + fetchStopInfoWindow(marker); + } else { + for (var i=0; i<stops.length; ++i) { + addStopMarkerFromList(stops[i], true); + } + } + } + + function stopTextSearchSubmit() { + var text = document.getElementById("stopTextSearchInput").value; + var url = "/json/stopsearch?q=" + text; // TODO URI escape + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayStops); + } + + function tripTextSearchSubmit() { + var text = document.getElementById("tripTextSearchInput").value; + selectTrip(text); + } + + /** + * Add stops markers to the map and remove stops no longer in the + * background. + */ + function callbackDisplayStopsBackground(data, responseCode) { + if (responseCode != 200) { + return; + } + var stops = eval(data); + // Make a list of all background markers + var oldStopMarkers = {}; + for (var stopId in stopMarkersBackground) { + oldStopMarkers[stopId] = 1; + } + // Add new markers to the map and remove from oldStopMarkers + for (var i=0; i<stops.length; ++i) { + var marker = addStopMarkerFromList(stops[i], false); + if (oldStopMarkers[marker.stopId]) { + delete oldStopMarkers[marker.stopId]; + } + } + // Delete all markers that remain in oldStopMarkers + for (var stopId in oldStopMarkers) { + GEvent.removeListener(stopMarkersBackground[stopId].clickListener); + map.removeOverlay(stopMarkersBackground[stopId]); + delete stopMarkersBackground[stopId] + } + } + + /** + * Remove all overlays from the map + */ + function clearMap() { + boundsOfPolyLine = null; + for (var stopId in stopMarkersSelected) { + GEvent.removeListener(stopMarkersSelected[stopId].clickListener); + } + for (var stopId in stopMarkersBackground) { + GEvent.removeListener(stopMarkersBackground[stopId].clickListener); + } + stopMarkersSelected = {}; + stopMarkersBackground = {}; + map.clearOverlays(); + } + + /** + * Return a new GIcon used for stops + */ + function makeStopIcon() { + var icon = new GIcon(); + icon.iconSize = new GSize(12, 20); + icon.shadowSize = new GSize(22, 20); + icon.iconAnchor = new GPoint(6, 20); + icon.infoWindowAnchor = new GPoint(5, 1); + return icon; + } + + /** + * Initialize icons. Call once during load. + */ + function initIcons() { + iconSelected = makeStopIcon(); + iconSelected.image = "/file/mm_20_yellow.png"; + iconSelected.shadow = "/file/mm_20_shadow.png"; + iconBackground = makeStopIcon(); + iconBackground.image = "/file/mm_20_blue_trans.png"; + iconBackground.shadow = "/file/mm_20_shadow_trans.png"; + iconBackgroundStation = makeStopIcon(); + iconBackgroundStation.image = "/file/mm_20_red_trans.png"; + iconBackgroundStation.shadow = "/file/mm_20_shadow_trans.png"; + } + + var iconSelected; + var iconBackground; + var iconBackgroundStation; + // Map from stopId to GMarker object for stops selected because they are + // part of a trip, etc + var stopMarkersSelected = {}; + // Map from stopId to GMarker object for stops found by the background + // passive search + var stopMarkersBackground = {}; + /** + * Add a stop to the map, given a row from stops.txt. + */ + function addStopMarkerFromList(list, selected, text) { + return addStopMarker(list[0], list[1], list[2], list[3], list[4], selected, text); + } + + /** + * Add a stop to the map, returning the new marker + */ + function addStopMarker(stopId, stopName, stopLat, stopLon, locationType, selected, text) { + if (stopMarkersSelected[stopId]) { + // stop was selected + var marker = stopMarkersSelected[stopId]; + if (text) { + oldText = marker.getText(); + if (oldText) { + oldText = oldText + "<br>"; + } + marker.setText(oldText + text); + } + return marker; + } + if (stopMarkersBackground[stopId]) { + // Stop was in the background. Either delete it from the background or + // leave it where it is. + if (selected) { + map.removeOverlay(stopMarkersBackground[stopId]); + delete stopMarkersBackground[stopId]; + } else { + return stopMarkersBackground[stopId]; + } + } + + var icon; + if (selected) { + icon = iconSelected; + } else if (locationType == 1) { + icon = iconBackgroundStation + } else { + icon = iconBackground; + } + var ll = new GLatLng(stopLat,stopLon); + var marker; + if (selected || text) { + if (!text) { + text = ""; // Make sure every selected icon has a text box, even if empty + } + var markerOpts = new Object(); + markerOpts.icon = icon; + markerOpts.labelText = text; + markerOpts.labelClass = "tooltip"; + markerOpts.labelOffset = new GSize(6, -20); + marker = new LabeledMarker(ll, markerOpts); + } else { + marker = new GMarker(ll, {icon: icon, draggable: !forbid_editing}); + } + marker.stopName = stopName; + marker.stopId = stopId; + if (selected) { + stopMarkersSelected[stopId] = marker; + } else { + stopMarkersBackground[stopId] = marker; + } + map.addOverlay(marker); + marker.clickListener = GEvent.addListener(marker, "click", function() {fetchStopInfoWindow(marker);}); + GEvent.addListener(marker, "dragend", function() { + + document.getElementById("edit").style.visibility = "visible"; + document.getElementById("edit_status").innerHTML = "updating..." + changeStopLocation(marker); + }); + return marker; + } + + /** + * Sends new location of a stop to server. + */ + function changeStopLocation(marker) { + var url = "/json/setstoplocation?id=" + + encodeURIComponent(marker.stopId) + + "&lat=" + encodeURIComponent(marker.getLatLng().lat()) + + "&lng=" + encodeURIComponent(marker.getLatLng().lng()); + GDownloadUrl(url, function(data, responseCode) { + document.getElementById("edit_status").innerHTML = unescape(data); + } ); + if (log) + GLog.writeUrl(url); + } + + /** + * Saves the current state of the data file opened at server side to file. + */ + function saveData() { + var url = "/json/savedata"; + GDownloadUrl(url, function(data, responseCode) { + document.getElementById("edit_status").innerHTML = data;} ); + if (log) + GLog.writeUrl(url); + } + + /** + * Fetch the next departing trips from the stop for display in an info + * window. + */ + function fetchStopInfoWindow(marker) { + var url = "/json/stoptrips?stop=" + encodeURIComponent(marker.stopId) + "&time=" + parseTimeInput() + "&date=" + parseDateInput(); + GDownloadUrl(url, function(data, responseCode) { + callbackDisplayStopInfoWindow(marker, data, responseCode); } ); + if (log) + GLog.writeUrl(url); + } + + function callbackDisplayStopInfoWindow(marker, data, responseCode) { + if (responseCode != 200) { + return; + } + var timeTrips = eval(data); + var html = "<b>" + marker.stopName + "</b> (" + marker.stopId + ")<br>"; + var latLng = marker.getLatLng(); + html = html + "(" + latLng.lat() + ", " + latLng.lng() + ")<br>"; + html = html + "<table><tr><th>service_id<th>time<th>name</tr>"; + for (var i=0; i < timeTrips.length; ++i) { + var time = timeTrips[i][0]; + var tripid = timeTrips[i][1][0]; + var tripname = timeTrips[i][1][1]; + var service_id = timeTrips[i][1][2]; + var timepoint = timeTrips[i][2]; + html = html + "<tr onClick='map.closeInfoWindow();selectTrip(\"" + + tripid + "\")'>" + + "<td>" + service_id + + "<td align='right'>" + (timepoint ? "" : "~") + + formatTime(time) + "<td>" + tripname + "</tr>"; + } + html = html + "</table>"; + marker.openInfoWindowHtml(html); + } + + function leadingZero(digit) { + if (digit < 10) + return "0" + digit; + else + return "" + digit; + } + + function formatTime(secSinceMidnight) { + var hours = Math.floor(secSinceMidnight / 3600); + var suffix = ""; + + if (twelveHourTime) { + suffix = (hours >= 12) ? "p" : "a"; + suffix += (hours >= 24) ? " next day" : ""; + hours = hours % 12; + if (hours == 0) + hours = 12; + } + var minutes = Math.floor(secSinceMidnight / 60) % 60; + var seconds = secSinceMidnight % 60; + if (seconds == 0) { + return hours + ":" + leadingZero(minutes) + suffix; + } else { + return hours + ":" + leadingZero(minutes) + ":" + leadingZero(seconds) + suffix; + } + } + + function parseTimeInput() { + var text = document.getElementById("timeInput").value; + var m = text.match(/([012]?\d):([012345]?\d)(:([012345]?\d))?/); + if (m) { + var seconds = parseInt(m[1], 10) * 3600; + seconds += parseInt(m[2], 10) * 60; + if (m[4]) { + second += parseInt(m[4], 10); + } + return seconds; + } else { + if (log) + GLog.write("Couldn't match " + text + " to time"); + return ""; + } + } + + function parseDateInput() { + var text = document.getElementById("startDateInput").value; + var m = text.match(/(19|20\d\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])/); + if (m) { + return text; + } else { + if (log) + GLog.write("Couldn't match " + text + " to date"); + return ""; + } + } + + /** + * Create a string of dots that gets longer with the log of count. + */ + function countToRepeatedDots(count) { + // Find ln_2(count) + 1 + var logCount = Math.ceil(Math.log(count) / 0.693148) + 1; + return new Array(logCount + 1).join("."); + } + + function fetchRoutes() { + url = "/json/routes"; + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayRoutes); + } + + function callbackDisplayRoutes(data, responseCode) { + if (responseCode != 200) { + patternDiv.appendChild(div); + } + var routes = eval(data); + var routesList = document.getElementById("routeList"); + while (routesList.hasChildNodes()) { + routesList.removeChild(routesList.firstChild); + } + for (i = 0; i < routes.length; ++i) { + var routeId = routes[i][0]; + var shortName = document.createElement("span"); + shortName.className = "shortName"; + shortName.appendChild(document.createTextNode(routes[i][1] + " ")); + var routeName = routes[i][2]; + var elem = document.createElement("div"); + elem.appendChild(shortName); + elem.appendChild(document.createTextNode(routeName)); + elem.id = "route_" + routeId; + elem.className = "routeChoice"; + elem.title = routeName; + GEvent.addDomListener(elem, "click", makeClosure(selectRoute, routeId)); + + var routeContainer = document.createElement("div"); + routeContainer.id = "route_container_" + routeId; + routeContainer.className = "routeContainer"; + routeContainer.appendChild(elem); + routesList.appendChild(routeContainer); + } + } + + function selectRoute(routeId) { + var routesList = document.getElementById("routeList"); + routeSpans = routesList.getElementsByTagName("div"); + for (var i = 0; i < routeSpans.length; ++i) { + if (routeSpans[i].className == "routeChoiceSelected") { + routeSpans[i].className = "routeChoice"; + } + } + + // remove any previously-expanded route + var tripInfo = document.getElementById("tripInfo"); + if (tripInfo) + tripInfo.parentNode.removeChild(tripInfo); + + selectedRoute = routeId; + var span = document.getElementById("route_" + routeId); + span.className = "routeChoiceSelected"; + fetchPatterns(routeId); + } + + function fetchPatterns(routeId) { + url = "/json/routepatterns?route=" + encodeURIComponent(routeId) + "&time=" + parseTimeInput() + "&date=" + parseDateInput(); + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayPatterns); + } + + function callbackDisplayPatterns(data, responseCode) { + if (responseCode != 200) { + return; + } + var div = document.createElement("div"); + div.className = "tripSection"; + div.id = "tripInfo"; + var firstTrip = null; + var patterns = eval(data); + clearMap(); + for (i = 0; i < patterns.length; ++i) { + patternDiv = document.createElement("div") + patternDiv.className = 'patternSection'; + div.appendChild(patternDiv) + var pat = patterns[i]; // [patName, patId, len(early trips), trips, len(later trips), has_non_zero_trip_type] + if (pat[5] == '1') { + patternDiv.className += " unusualPattern" + } + patternDiv.appendChild(document.createTextNode(pat[0])); + patternDiv.appendChild(document.createTextNode(", " + (pat[2] + pat[3].length + pat[4]) + " trips: ")); + if (pat[2] > 0) { + patternDiv.appendChild(document.createTextNode(countToRepeatedDots(pat[2]) + " ")); + } + for (j = 0; j < pat[3].length; ++j) { + var trip = pat[3][j]; + var tripId = trip[1]; + if ((i == 0) && (j == 0)) + firstTrip = tripId; + patternDiv.appendChild(document.createTextNode(" ")); + var span = document.createElement("span"); + span.appendChild(document.createTextNode(formatTime(trip[0]))); + span.id = "trip_" + tripId; + GEvent.addDomListener(span, "click", makeClosure(selectTrip, tripId)); + patternDiv.appendChild(span) + span.className = "tripChoice"; + } + if (pat[4] > 0) { + patternDiv.appendChild(document.createTextNode(" " + countToRepeatedDots(pat[4]))); + } + patternDiv.appendChild(document.createElement("br")); + } + route = document.getElementById("route_container_" + selectedRoute); + route.appendChild(div); + if (tripId != null) + selectTrip(firstTrip); + } + + // Needed to get around limitation in javascript scope rules. + // See http://calculist.blogspot.com/2005/12/gotcha-gotcha.html + function makeClosure(f, a, b, c) { + return function() { f(a, b, c); }; + } + function make1ArgClosure(f, a, b, c) { + return function(x) { f(x, a, b, c); }; + } + function make2ArgClosure(f, a, b, c) { + return function(x, y) { f(x, y, a, b, c); }; + } + + function selectTrip(tripId) { + var tripInfo = document.getElementById("tripInfo"); + if (tripInfo) { + tripSpans = tripInfo.getElementsByTagName('span'); + for (var i = 0; i < tripSpans.length; ++i) { + tripSpans[i].className = 'tripChoice'; + } + } + var span = document.getElementById("trip_" + tripId); + // Won't find the span if a different route is selected + if (span) { + span.className = 'tripChoiceSelected'; + } + clearMap(); + url = "/json/tripstoptimes?trip=" + encodeURIComponent(tripId); + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayTripStopTimes); + fetchTripPolyLine(tripId); + fetchTripRows(tripId); + } + + function callbackDisplayTripStopTimes(data, responseCode) { + if (responseCode != 200) { + return; + } + var stopsTimes = eval(data); + if (!stopsTimes) return; + displayTripStopTimes(stopsTimes[0], stopsTimes[1]); + } + + function fetchTripPolyLine(tripId) { + url = "/json/tripshape?trip=" + encodeURIComponent(tripId); + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, callbackDisplayTripPolyLine); + } + + function callbackDisplayTripPolyLine(data, responseCode) { + if (responseCode != 200) { + return; + } + var points = eval(data); + if (!points) return; + displayPolyLine(points); + } + + var boundsOfPolyLine = null; + function expandBoundingBox(latLng) { + if (boundsOfPolyLine == null) { + boundsOfPolyLine = new GLatLngBounds(latLng, latLng); + } else { + boundsOfPolyLine.extend(latLng); + } + } + + /** + * Display a line given a list of points + * + * @param {Array} List of lat,lng pairs + */ + function displayPolyLine(points) { + var linePoints = Array(); + for (i = 0; i < points.length; ++i) { + var ll = new GLatLng(points[i][0], points[i][1]); + expandBoundingBox(ll); + linePoints[linePoints.length] = ll; + } + var polyline = new GPolyline(linePoints, "#FF0000", 4); + map.addOverlay(polyline); + map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine)); + } + + function displayTripStopTimes(stops, times) { + for (i = 0; i < stops.length; ++i) { + var marker; + if (times && times[i] != null) { + marker = addStopMarkerFromList(stops[i], true, formatTime(times[i])); + } else { + marker = addStopMarkerFromList(stops[i], true); + } + expandBoundingBox(marker.getPoint()); + } + map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine)); + } + + function fetchTripRows(tripId) { + url = "/json/triprows?trip=" + encodeURIComponent(tripId); + if (log) + GLog.writeUrl(url); + GDownloadUrl(url, make2ArgClosure(callbackDisplayTripRows, tripId)); + } + + function callbackDisplayTripRows(data, responseCode, tripId) { + if (responseCode != 200) { + return; + } + var rows = eval(data); + if (!rows) return; + var html = ""; + for (var i = 0; i < rows.length; ++i) { + var filename = rows[i][0]; + var row = rows[i][1]; + html += "<b>" + filename + "</b>: " + formatDictionary(row) + "<br>"; + } + html += svgTag("/ttablegraph?height=100&trip=" + tripId, "height='115' width='100%'"); + var bottombarDiv = document.getElementById("bottombar"); + bottombarDiv.style.display = "block"; + bottombarDiv.style.height = "175px"; + bottombarDiv.innerHTML = html; + sizeRouteList(); + } + + /** + * Return HTML to embed a SVG object in this page. src is the location of + * the SVG and attributes is inserted directly into the object or embed + * tag. + */ + function svgTag(src, attributes) { + if (navigator.userAgent.toLowerCase().indexOf("msie") != -1) { + if (isSVGControlInstalled()) { + return "<embed pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + src + "' " + attributes +"></embed>"; + } else { + return "<p>Please install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a> to get SVG support in IE</p>"; + } + } else { + return "<object data='" + src + "' type='image/svg+xml' " + attributes + "><p>No SVG support in your browser. Try Firefox 1.5 or newer or install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a></p></object>"; + } + } + + /** + * Format an Array object containing key-value pairs into a human readable + * string. + */ + function formatDictionary(d) { + var output = ""; + var first = 1; + for (var k in d) { + if (first) { + first = 0; + } else { + output += " "; + } + output += "<b>" + k + "</b>=" + d[k]; + } + return output; + } + + + function windowHeight() { + // Standard browsers (Mozilla, Safari, etc.) + if (self.innerHeight) + return self.innerHeight; + // IE 6 + if (document.documentElement && document.documentElement.clientHeight) + return document.documentElement.clientHeight; + // IE 5 + if (document.body) + return document.body.clientHeight; + // Just in case. + return 0; + } + + function sizeRouteList() { + var bottombarHeight = 0; + var bottombarDiv = document.getElementById('bottombar'); + if (bottombarDiv.style.display != 'none') { + bottombarHeight = document.getElementById('bottombar').offsetHeight + + document.getElementById('bottombar').style.marginTop; + } + var height = windowHeight() - document.getElementById('topbar').offsetHeight - 15 - bottombarHeight; + document.getElementById('content').style.height = height + 'px'; + if (map) { + // Without this displayPolyLine does not use the correct map size + map.checkResize(); + } + } + + var calStartDate = new CalendarPopup(); + calStartDate.setReturnFunction("setStartDate"); + + function maybeAddLeadingZero(number) { + if(number > 10) + { + return number; + } + return '0' + number; + } + + function setStartDate(y,m,d) { + document.getElementById('startDateInput').value = y + maybeAddLeadingZero(m) + maybeAddLeadingZero(d); + } + + //]]> + </script> + </head> + +<body class='sidebar-left' onload="load();" onunload="GUnload()" onresize="sizeRouteList()"> +<div id='topbar'> +<div id="edit"> + <span id="edit_status">...</span> + <form onSubmit="saveData(); return false;"><input value="Save" type="submit"> +</div> +<div id="agencyHeader">[agency]</div> +</div> +<div id='content'> + <div id='sidebar-wrapper'><div id='sidebar'> + Time: <input type="text" value="8:00" width="9" id="timeInput"><br> + Date: <input type="text" value="" size="8" id="startDateInput" name="startDateInput"> <a href="#" onclick="calStartDate.select(document.getElementById('startDateInput'),'startDateInput','yyyyMMdd'); return false;">select</a><br> + <form onSubmit="stopTextSearchSubmit(); return false;"> + Find Station: <input type="text" id="stopTextSearchInput"><input value="Search" type="submit"></form><br> + <form onSubmit="tripTextSearchSubmit(); return false;"> + Find Trip ID: <input type="text" id="tripTextSearchInput"><input value="Search" type="submit"></form><br> + <div id="routeList">routelist</div> + </div></div> + + <div id='map-wrapper'> <div id='map'></div> </div> +</div> + +<div id='bottombar'>bottom bar</div> + +</body> +</html> +