PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/share/spice/flights/route/flights_route.js

http://github.com/duckduckgo/zeroclickinfo-spice
JavaScript | 411 lines | 316 code | 66 blank | 29 comment | 49 complexity | c485189256787e6b091cd09e80fffbf0 MD5 | raw file
Possible License(s): Apache-2.0
  1. (function(env) {
  2. "use strict";
  3. var path = "/js/spice/flights",
  4. id = 'route',
  5. // store the user's airline and city search information
  6. queriedAirlines = null,
  7. sourceCity = null,
  8. destinationCity = null,
  9. // min number of minutes to be considered as 'delay'
  10. MIN_DELAY_MINUTES= 5,
  11. // define some static variables for callback and helpers
  12. MILLIS_PER_MIN = 60000,
  13. MILLIS_PER_HOUR = MILLIS_PER_MIN * 60,
  14. STATUS = {
  15. "S": "Scheduled",
  16. "A": "In the air",
  17. "U": "Unknown status",
  18. "R": "Redirected flight",
  19. "L": "Landed",
  20. "D": "Diverted",
  21. "C": "Cancelled",
  22. "NO": "Not Operational",
  23. "DN": "Data Needed"
  24. };
  25. // this function parses the initial query and sends out additional queries
  26. // as necessary for cities with multiple airports
  27. env.ddg_spice_flights_route = function(api_result){
  28. // prevent jQuery from appending "_={timestamp}" in our url when we use $.getScript
  29. $.ajaxSetup({ cache: true });
  30. // Get the original query to continue polling for other potential airports
  31. // and for filtering results by airline
  32. var script = $('[src*="/js/spice/flights/route/"]')[0],
  33. source = $(script).attr("src"),
  34. match = source.match(/\/js\/spice\/flights\/route\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)\/(.*)/),
  35. queriedSrcAirports = match[2].split('%2C'),
  36. queriedDstAirports = match[3].split('%2C');
  37. queriedAirlines = match[1].split('%2C');
  38. sourceCity = match[10].replace('%2B', "+");
  39. destinationCity = match[11].replace('%2B', "+");
  40. // create all remaining source/destination pairs that have not yet been polled for
  41. for (var srcCounter = 0; srcCounter < queriedSrcAirports.length; srcCounter++) {
  42. for (var dstCounter = 0; dstCounter < queriedDstAirports.length; dstCounter++) {
  43. // skip the first pair because the initial spice-to already
  44. // obtained this result
  45. if (srcCounter === 0 && dstCounter === 0)
  46. continue;
  47. $.getJSON(path +
  48. "/route_helper/" +
  49. queriedSrcAirports[srcCounter] + "/" +
  50. queriedDstAirports[dstCounter] + "/arr/" +
  51. match[6] + "/" + match[7] + "/" + match[8] +
  52. "/" + match[9], display);
  53. }
  54. }
  55. display(api_result);
  56. };
  57. // this function displays flight results
  58. function display (api_result) {
  59. // Check if we have anything returned.
  60. if (!api_result || !api_result.flightStatuses || !api_result.appendix.airlines) {
  61. return Spice.failed('route');
  62. }
  63. DDG.require('moment.js', function() {
  64. // setup Moment.js
  65. moment.locale('en', {
  66. relativeTime : {
  67. future: "in %s",
  68. past: "%s ago",
  69. s: "s",
  70. m: "1m",
  71. mm: "%dm",
  72. h: "1h",
  73. hh: "%dh",
  74. d: "1d",
  75. dd: "%dd",
  76. M: "1mo",
  77. MM: "%dmo",
  78. y: "1y",
  79. yy: "%dy"
  80. }
  81. });
  82. // Fine-grained relative thresholds for humanize times
  83. moment.relativeTimeThreshold('m', 59);
  84. moment.relativeTimeThreshold('h', 23);
  85. // Create dictionaries to convert FlightStat codes to ICAO codes/airline names
  86. var dictFlightStats = {};
  87. $.each(api_result.appendix.airlines, function(index, value) {
  88. dictFlightStats[value.fs] = {
  89. "icao": value.icao,
  90. "name": value.name,
  91. };
  92. });
  93. // Create dictionaries to convert FlightStat codes to ICAO codes/airline names
  94. var dictAirportStats = {};
  95. $.each(api_result.appendix.airports, function(index, value) {
  96. dictAirportStats[value.fs] = value;
  97. });
  98. // Check if flight is an array or not.
  99. var flight = [];
  100. if (!($.isArray(api_result.flightStatuses))) {
  101. flight = [api_result.flightStatuses];
  102. } else {
  103. flight = api_result.flightStatuses;
  104. }
  105. // array of flights
  106. var results = [];
  107. var first_active_index = -1;
  108. // package up the flights into the result array
  109. for (var i = 0; i < flight.length; i++) {
  110. // compile all airline and codeshare codes for each returned flight
  111. var carrierCodes = [{"fsCode": flight[i].carrierFsCode, "flightNumber": flight[i].flightNumber}];
  112. if (flight[i].codeshares) {
  113. $.each(flight[i].codeshares, function(codeShareIndex, flightCodeShare) {
  114. carrierCodes.push(flightCodeShare);
  115. });
  116. }
  117. // this loop iterates through all codeshared codes for the current flight result
  118. // once we find a matching carrier code, we break out of this loop
  119. for (var carriersIndex = 0; carriersIndex < carrierCodes.length; carriersIndex++) {
  120. if (queriedAirlines.indexOf(dictFlightStats[carrierCodes[carriersIndex].fsCode].icao) !== -1) {
  121. var current = flight[i],
  122. opTime = current.operationalTimes;
  123. var departureDate =
  124. DDG.getProperty(opTime, "actualGateDeparture.dateUtc") ||
  125. DDG.getProperty(opTime, "estimatedGateDeparture.dateUtc") ||
  126. DDG.getProperty(opTime, "scheduledGateDeparture.dateUtc") ||
  127. DDG.getProperty(opTime, "flightPlanPlannedDeparture.dateUtc");
  128. var arrivalDate =
  129. DDG.getProperty(opTime, 'actualGateArrival.dateUtc') ||
  130. DDG.getProperty(opTime, 'estimatedGateArrival.dateUtc') ||
  131. DDG.getProperty(opTime, 'scheduledGateArrival.dateUtc') ||
  132. DDG.getProperty(opTime, 'flightPlanPlannedArrival.dateUtc');
  133. var scheduledDepartureDate =
  134. DDG.getProperty(opTime, 'scheduledGateDeparture.dateUtc') ||
  135. DDG.getProperty(opTime, 'flightPlanPlannedDeparture.dateUtc');
  136. var scheduledArrivalDate =
  137. DDG.getProperty(opTime, 'scheduledGateArrival.dateUtc') ||
  138. DDG.getProperty(opTime, 'flightPlanPlannedArrival.dateUtc');
  139. var scheduledDepartureDateLocal =
  140. DDG.getProperty(opTime, 'scheduledGateDeparture.dateLocal') ||
  141. DDG.getProperty(opTime, 'flightPlanPlannedDeparture.dateLocal');
  142. var scheduledArrivalDateLocal =
  143. DDG.getProperty(opTime, 'scheduledGateArrival.dateLocal') ||
  144. DDG.getProperty(opTime, 'flightPlanPlannedArrival.dateLocal');
  145. var departing_airport = dictAirportStats[current.departureAirportFsCode];
  146. var departing_location = [
  147. departing_airport.city,
  148. departing_airport.stateCode,
  149. departing_airport.countryCode
  150. ].filter(not_empty).join(', ');
  151. var arriving_airport = dictAirportStats[current.arrivalAirportFsCode];
  152. var arriving_location = [
  153. arriving_airport.city,
  154. arriving_airport.stateCode,
  155. arriving_airport.countryCode
  156. ].filter(not_empty).join(', ');
  157. var departing = {
  158. airportTimezone: "0",
  159. airport: departing_airport,
  160. location: departing_location,
  161. terminal: (current.airportResources && current.airportResources.departureTerminal) || "N/A",
  162. gate: (current.airportResources && current.airportResources.departureGate) || "N/A",
  163. isDeparted: true,
  164. departureDateF: moment(departureDate).format("ddd MMM, D"),
  165. },
  166. arriving = {
  167. airportTimezone: "0",
  168. airport: arriving_airport,
  169. location: arriving_location,
  170. terminal: (current.airportResources && current.airportResources.arrivalTerminal) || "N/A",
  171. gate: (current.airportResources && current.airportResources.arrivalGate) || "N/A",
  172. isDeparted: false
  173. };
  174. var status = getStatus(current);
  175. results.push({
  176. flight: flight[i],
  177. airlineName: dictFlightStats[carrierCodes[carriersIndex].fsCode].name,
  178. airlineCode: carrierCodes[carriersIndex].fsCode,
  179. flightNumber: carrierCodes[carriersIndex].flightNumber,
  180. departing: departing,
  181. arriving: arriving,
  182. departureDate: departureDate,
  183. arrivalDate: arrivalDate,
  184. scheduledDepartureDate: scheduledDepartureDate,
  185. scheduledArrivalDate: scheduledArrivalDate,
  186. scheduledDepartureDateLocal: scheduledDepartureDateLocal,
  187. scheduledArrivalDateLocal: scheduledArrivalDateLocal,
  188. status: status[0],
  189. is_on_time: status[1],
  190. statusColor: getStatusColor(status[1]),
  191. delayTime: status[2]
  192. });
  193. if (first_active_index == -1 && !(status[0] === 'Landed' || status[0] === 'Cancelled')) {
  194. first_active_index = results.length - 1;
  195. }
  196. break;
  197. }
  198. }
  199. }
  200. if (results.length === 0) {
  201. return Spice.failed('route');
  202. }
  203. Spice.add({
  204. data: results,
  205. id: "route",
  206. name: "Route",
  207. meta: {
  208. minItemsForModeSwitch: 3,
  209. sourceName: 'FlightStats',
  210. sourceUrl: "http://www.flightstats.com/go/FlightStatus/flightStatusByRoute.do?" +
  211. "departure=" + sourceCity +
  212. "&arrival=" + destinationCity,
  213. itemType: 'flights',
  214. disableMobileGrid: true,
  215. selectedItem: first_active_index == -1 ? 0 : first_active_index,
  216. scrollToSelectedItem: true
  217. },
  218. normalize: function(item) {
  219. var status_text,
  220. display_datetime,
  221. delay_time = item.delayTime;
  222. if (item.status === 'Cancelled'){
  223. status_text = 'Cancelled';
  224. } else if (moment(item.arrivalDate).isBefore(moment().utc())) {
  225. status_text = 'Arrived';
  226. display_datetime = moment(item.arrivalDate);
  227. } else {
  228. if (moment(item.departureDate).isAfter(moment().utc())) {
  229. status_text = 'Departs';
  230. display_datetime = moment(item.departureDate);
  231. } else {
  232. status_text = 'Arrives';
  233. display_datetime = moment(item.arrivalDate);
  234. }
  235. }
  236. var scheduled_arrival = moment(item.scheduledArrivalDateLocal),
  237. scheduled_depart = moment(item.scheduledDepartureDateLocal),
  238. same_date = scheduled_arrival.isSame(scheduled_depart, "day");
  239. var progress_max = 98,
  240. progress_percent = 100,
  241. scheduled_progress_percent = 100,
  242. time_total = moment(item.scheduledArrivalDate).diff(item.scheduledDepartureDate),
  243. time_remaining = moment(item.scheduledArrivalDate).diff(moment().utc());
  244. if (status_text !== 'Arrived' && status_text !== 'Cancelled') {
  245. scheduled_progress_percent = progress_max * (time_total - time_remaining) / time_total;
  246. if (delay_time) progress_percent = progress_max * (time_total - time_remaining - (delay_time * MILLIS_PER_MIN)) / time_total;
  247. if (scheduled_progress_percent < 0) scheduled_progress_percent = 0;
  248. if (scheduled_progress_percent > progress_max) scheduled_progress_percent = progress_max;
  249. if (progress_percent < 0) progress_percent = 0;
  250. if (progress_percent > scheduled_progress_percent) progress_percent = scheduled_progress_percent;
  251. }
  252. var year = moment(item.departureDate).year(),
  253. month = moment(item.departureDate).month() + 1,
  254. day = moment(item.departureDate).date();
  255. return {
  256. datetime: display_datetime ? display_datetime.fromNow() : "",
  257. delay_time: delay_time ? moment.duration(delay_time, "minutes").humanize() : delay_time,
  258. progress_percent: progress_percent,
  259. scheduled_progress_percent: scheduled_progress_percent,
  260. status_text: status_text,
  261. scheduled_arrival: same_date ? scheduled_arrival.format('LT') : scheduled_arrival.format('LT (MMM DD)'),
  262. scheduled_depart: same_date ? scheduled_depart.format('LT') : scheduled_depart.format('LT (MMM DD)'),
  263. has_landed: item.status === 'Landed',
  264. is_cancelled: item.status === 'Cancelled',
  265. has_arrived: status_text === 'Arrived' || status_text === 'Cancelled',
  266. url: "http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?" +
  267. "airlineCode=" + item.airlineCode +
  268. "&flightNumber=" + item.flightNumber +
  269. "&departureDate=" + year + "-" + month + "-" + day
  270. };
  271. },
  272. sort_fields: {
  273. departureDate: function(a, b) {
  274. a = moment(a.departureDate);
  275. b = moment(b.departureDate);
  276. return ((a < b) ? -1 : (a > b ? 1 : 0));
  277. }
  278. },
  279. sort_default: 'departureDate',
  280. templates: {
  281. group: 'base',
  282. detail: false,
  283. options: {
  284. content: Spice.flights_route.content
  285. },
  286. variants: {
  287. tile: 'xwide'
  288. },
  289. elClass: {
  290. tile: 'whole--screen-s'
  291. }
  292. },
  293. onShow: function(item) {
  294. // Give landed flights a grey background
  295. $('.tile__landed, .tile__cancelled').parents('.tile__body').addClass('bg-clr--silver-light');
  296. }
  297. });
  298. });
  299. }
  300. // Check if the airplane is on-time or delayed.
  301. function getStatus(flight) {
  302. function formatDelayTime(delayTime){
  303. return (!delayTime || (delayTime < MIN_DELAY_MINUTES))? false : delayTime;
  304. }
  305. var status,
  306. delayTime = false;
  307. switch (flight.status) {
  308. // Flight cancelled
  309. case "C":
  310. status = ["Cancelled", false, delayTime];
  311. break;
  312. // Flight in-progress
  313. case "A":
  314. delayTime = formatDelayTime(DDG.getProperty(flight.delays, "arrivalGateDelayMinutes") ||
  315. DDG.getProperty(flight.delays, "departureGateDelayMinutes") ||
  316. DDG.getProperty(flight.delays, "arrivalRunwayDelayMinutes"));
  317. if (delayTime) {
  318. status = ["Delayed", false, delayTime];
  319. } else {
  320. status = ["On Time", true, delayTime];
  321. }
  322. break;
  323. // Flight scheduled
  324. case "S":
  325. delayTime = formatDelayTime(DDG.getProperty(flight.delays, "departureGateDelayMinutes"));
  326. if (delayTime) {
  327. status = ["Delayed", false, delayTime];
  328. } else {
  329. status = ["On Time", true, delayTime];
  330. }
  331. break;
  332. // Flight delayed, re-routed, status unknown, etc
  333. default:
  334. status = [STATUS[flight.status], true, delayTime];
  335. break;
  336. }
  337. return status;
  338. }
  339. function getStatusColor (onTime) {
  340. return onTime ? 'clr--green' : 'clr--red';
  341. }
  342. function not_empty (value) {
  343. return value !== undefined && value !== '';
  344. }
  345. }(this));