func performancePlotHandlerV2(w http.ResponseWriter, r *http.Request) { /* Extract fields in GET request */ line := r.URL.Query().Get("line") bound := r.URL.Query().Get("bound") origin := r.URL.Query().Get("origin") destination := r.URL.Query().Get("destination") departureTime := parseISOTime(r.URL.Query().Get("departureTime")) /* Error message string that functions should append to */ errorMessage := "" /* Some additional info for nice display*/ originName := selectdb.SelectStop(origin) destinationName := selectdb.SelectStop(destination) /* Find out which vehicle produced the data and list its journey history */ vehicleId, vehicleHistory, actualJourneyTime, totalPredictions, totalStddevs := lookupVehicleHistory2(line, bound, origin, destination, departureTime, &errorMessage) /* Write HTML output */ writePerformancePlotDetails2(w, PerformancePlotDetails2{ ErrorMessage: errorMessage, Line: line, Bound: bound, Origin: origin, DepartureTime: departureTime.Format(timeFormatString), OriginName: originName, DestinationName: destinationName, VehicleId: vehicleId, VehicleHistory: vehicleHistory, PredictionMethods: performanceeval.Methods(), ActualTime: actualJourneyTime, Predictions: totalPredictions, Stddevs: totalStddevs, }) }
/* Grabs all stored historic and real time data, produce time plots and also percentages of consistently delayed or early */ func DelayConsistencyPlot(startTime, endTime time.Time, wr io.Writer, templatePath string) { charts := make([]DelayConsistencyChart, 0) availableRoutes := selectdb.SelectAvailableRoutesTflPredictions() for _, route := range availableRoutes { // Extract fields from routes line := route["line"] bound := route["bound"] fromStop := route["fromStop"] toStop := route["toStop"] fromStopName := selectdb.SelectStop(fromStop) toStopName := selectdb.SelectStop(toStop) actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime) // Extract the actual departure times for generating predictions later on (discarding those that are) actualDepartureTimes := make([]time.Time, len(actualJourneyTimes)) actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes)) i := 0 for departureTime, duration := range actualJourneyTimes { actualDepartureTimes[i] = departureTime actualJourneyTimeDurations[i] = duration i++ } // Fetch Tfl predictions // tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes) /* Cache for real time estimates which can be reused for different prediction methods map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */ realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64) // Generate predictions using our methods (best historic and real time only) predictions := make(map[performanceeval.Method]map[time.Time]time.Duration) predictionsAdjacentStops := make(map[performanceeval.Method]map[time.Time]map[string]map[string]time.Duration) methods := []performanceeval.Method{ performanceeval.DefaultHistoric(), performanceeval.DefaultRealTime(), } // Highest # consistency possible - number of pairs of adjacent stops in route maxScore := 0 for _, method := range methods { predictions[method], predictionsAdjacentStops[method], maxScore = predictJourneyTimesWithDetails(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache) } // Convert the above into PerformanceTestSeries2 actualJourneyTimesSeries := PerformanceTestSeries2{ SeriesName: "Actual Time", X: actualDepartureTimes, Y: actualJourneyTimeDurations, Count: len(actualJourneyTimeDurations), } sort.Sort(actualJourneyTimesSeries) /*tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes) sort.Sort(tflPredictionsSeries)*/ performanceTestSeries2 := []PerformanceTestSeries2{ actualJourneyTimesSeries, // tflPredictionsSeries, } // Add all prediction series into performance test series 2 for method, predictedJouneyTimes := range predictions { predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes) sort.Sort(predictionSeries) performanceTestSeries2 = append(performanceTestSeries2, predictionSeries) } // Form delay consistency (percentages) series delayConsistencySeries := getDelayConsistencySeries(predictionsAdjacentStops[performanceeval.DefaultHistoric()], predictionsAdjacentStops[performanceeval.DefaultRealTime()], maxScore) charts = append(charts, DelayConsistencyChart{ Line: line, Bound: bound, Origin: fromStop, Destination: toStop, OriginName: fromStopName, DestinationName: toStopName, MultipleSeries: performanceTestSeries2, MultiplePercentageSeries: []DelayConsistencySeries{delayConsistencySeries}, ServerDomain: credentials.GetGoServerDomain(), }) logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop) } summary := DelayConsistencyPlotSummary{ Methods: performanceeval.Methods(), Charts: charts, } writeDelayConsistencyChart(wr, summary, templatePath) }
/* Find out which vehicle produced the data and list its journey history, together with the total actual journey time, total predictions and total standard deviations */ func lookupVehicleHistory2(line, bound, origin, destination string, departureTime time.Time, errorMessage *string) (string, []VehicleRecord2, time.Duration, map[performanceeval.Method]time.Duration, map[performanceeval.Method]time.Duration) { vehicleId, arrivalTime, err := selectdb.SelectVehicleAndArrivalTimeFromJourneyHistory(line, bound, origin, destination, departureTime) if err != nil { logger.GetLogger().Panic(err) } startTime := departureTime endTime := arrivalTime vehicleHistory := selectdb.SelectVehicleHistory(vehicleId, startTime, endTime, timeFormatString) rawArrivalTimes := selectdb.SelectRawArrivalTimes(vehicleId, startTime, endTime, timeFormatString) intermediateStopsOrdered := selectdb.SelectIntermediateStops(line, bound, origin, destination) intermediateStops := make(map[string]map[string]bool) for i := 0; i < len(intermediateStopsOrdered)-1; i++ { intermediateStops[intermediateStopsOrdered[i]] = map[string]bool{ intermediateStopsOrdered[i+1]: true, } } /* Temporarily discarded */ // predictionsUsedRealTime := make(map[string]map[string]map[time.Time]map[string]int64) totalPredictions := make(map[performanceeval.Method]time.Duration) totalStddevs := make(map[performanceeval.Method]time.Duration) idvPredictions := make(map[performanceeval.Method]map[string]map[string]time.Duration) idvStddevs := make(map[performanceeval.Method]map[string]map[string]time.Duration) idvExpectedArrivalTimesShifting := make(map[performanceeval.Method]map[string]map[string]time.Time) idvMixedTypes := make(map[performanceeval.Method]map[string]map[string]string) for _, method := range performanceeval.Methods() { totalPrediction, totalStddev, idvPredictionsMap, idvStddevsMap, edtShifting, mixedTypes := lookupPredictionsUsed(line, bound, intermediateStops, intermediateStopsOrdered, departureTime, method, nil, errorMessage) totalPredictions[method] = totalPrediction totalStddevs[method] = totalStddev idvPredictions[method] = idvPredictionsMap idvStddevs[method] = idvStddevsMap if method.IsShiftingBased() { idvExpectedArrivalTimesShifting[method] = edtShifting } if method.IsMixedBased() { idvMixedTypes[method] = mixedTypes } } actualJourneyTime := performanceeval.FetchActualJourneyTime(line, bound, origin, destination, departureTime) /* Flag for use later, in case bus switches direction invalidate and prevent travel time details from further generated */ firstTimeFlag := true result := make([]VehicleRecord2, len(vehicleHistory)) for i, v := range vehicleHistory { result[i] = VehicleRecord2{ Line: v["line"], Bound: v["bound"], StopSeq: v["stopSeq"], StopName: v["stopName"], ArrivalTime: v["time"], RawArrivalTime: rawArrivalTimes[v["naptanId"]], NaptanId: v["naptanId"], Details: generateTravelTimeDetails(line, bound, intermediateStops, departureTime, vehicleHistory, i, idvPredictions, idvStddevs, idvExpectedArrivalTimesShifting, idvMixedTypes, &firstTimeFlag), } } if !firstTimeFlag { *errorMessage += "Bus terminated early\n" } /*predictions := []time.Duration{ actualJourneyTime, predictionThreeDays, predictionWeekly, predictionWeeklyDiscardInterpolation, predictionWeeklySameRouteOnly, predictionWeeklyShifting, predictionWeeklyShiftingDiscardInterpolation, predictionWeeklyShiftingSameRouteOnly, predictionRealTimeMedian, predictionRealTimeLastTwo, totalStddevThreeDays, totalStddevWeekly, totalStddevWeeklyDiscardInterpolation, totalStddevWeeklySameRouteOnly, totalStddevWeeklyShifting, totalStddevWeeklyShiftingDiscardInterpolation, totalStddevWeeklyShiftingSameRouteOnly, totalStddevRealTimeMedian, totalStddevRealTimeLastTwo, }*/ return vehicleId, result, actualJourneyTime, totalPredictions, totalStddevs }
func generateTravelTimeDetails(line, bound string, intermediateStops map[string]map[string]bool, departureTime time.Time, vehicleHistory []map[string]string, idx int, idvPredictions map[performanceeval.Method]map[string]map[string]time.Duration, idvStddevs map[performanceeval.Method]map[string]map[string]time.Duration, expectedArrivalTimesShifting map[performanceeval.Method]map[string]map[string]time.Time, mixedTypesAtEachStop map[performanceeval.Method]map[string]map[string]string, firstTimeFlag *bool) TravelTimeDetails { /* Cannot generate travel time details if it is the first row (since no previous stop) */ if idx == 0 { return TravelTimeDetails{ TimeTaken: time.Duration(0), Predictions: nil, Stddevs: nil, ExpectedArrivalTimesShifting: nil, MixedTypes: nil, } } fromTime := parseTime(vehicleHistory[idx-1]["time"]) toTime := parseTime(vehicleHistory[idx]["time"]) timeTaken := toTime.Sub(fromTime) checkline := vehicleHistory[idx]["line"] checkBound := vehicleHistory[idx]["bound"] /* If changed direction, invalidate flag and return nil, so that no further travel time details are generated */ if line != checkline || bound != checkBound || !*firstTimeFlag { *firstTimeFlag = false return TravelTimeDetails{ TimeTaken: timeTaken, Predictions: nil, Stddevs: nil, ExpectedArrivalTimesShifting: nil, MixedTypes: nil, } } fromStop := vehicleHistory[idx-1]["naptanId"] toStop := vehicleHistory[idx]["naptanId"] fromStopSeq, _ := strconv.Atoi(vehicleHistory[idx-1]["stopSeq"]) toStopSeq, _ := strconv.Atoi(vehicleHistory[idx]["stopSeq"]) if fromStopSeq+1 != toStopSeq { return TravelTimeDetails{ TimeTaken: timeTaken, Predictions: nil, Stddevs: nil, ExpectedArrivalTimesShifting: nil, MixedTypes: nil, } } predictions := make(map[performanceeval.Method]time.Duration) stddevs := make(map[performanceeval.Method]time.Duration) edtsShifting := make(map[performanceeval.Method]time.Time) mixedTypes := make(map[performanceeval.Method]string) for _, method := range performanceeval.Methods() { predictions[method] = idvPredictions[method][fromStop][toStop] stddevs[method] = idvStddevs[method][fromStop][toStop] if method.IsShiftingBased() { edtsShifting[method] = expectedArrivalTimesShifting[method][fromStop][toStop] } if method.IsMixedBased() { mixedTypes[method] = mixedTypesAtEachStop[method][fromStop][toStop] } } return TravelTimeDetails{ TimeTaken: timeTaken, Predictions: predictions, Stddevs: stddevs, ExpectedArrivalTimesShifting: edtsShifting, MixedTypes: mixedTypes, } }
return fmt.Sprintf("%v (s.d. %v)", predictions[method].String(), stddevs[method].String()) }, "formatErrorMessage": func(errorMessage string) string { return strings.Replace(errorMessage, "\n", "<br>", -1) }, "formatEAT": func(method performanceeval.Method, expectedArrivalTimes map[performanceeval.Method]time.Time) string { expectedArrivalTime, exists := expectedArrivalTimes[method] if exists { return formatTime(expectedArrivalTime) } else { return "" } }, "getTargetColumnArray": func() string { startColumn := 5 columns := make([]string, len(performanceeval.Methods())) for i := 0; i < len(performanceeval.Methods()); i++ { columns[i] = fmt.Sprintf("%v", startColumn+i) } return "[" + strings.Join(columns, ",") + "]" }, } ) type PerformancePlotDetails2 struct { ErrorMessage string Line string Bound string Origin string DepartureTime string OriginName string
/* Grabs all stored data to produce time plots */ func PerformancePlot2(startTime, endTime time.Time, wr io.Writer, templatePath string) { charts := make([]PerformanceChart2, 0) availableRoutes := selectdb.SelectAvailableRoutesTflPredictions() for _, route := range availableRoutes { // Extract fields from routes line := route["line"] bound := route["bound"] fromStop := route["fromStop"] toStop := route["toStop"] fromStopName := selectdb.SelectStop(fromStop) toStopName := selectdb.SelectStop(toStop) actualJourneyTimes := performanceeval.FetchActualJourneyTimes(line, bound, fromStop, toStop, startTime, endTime) // Extract the actual departure times for generating predictions later on (discarding those that are) actualDepartureTimes := make([]time.Time, len(actualJourneyTimes)) actualJourneyTimeDurations := make([]time.Duration, len(actualJourneyTimes)) i := 0 for departureTime, duration := range actualJourneyTimes { actualDepartureTimes[i] = departureTime actualJourneyTimeDurations[i] = duration i++ } // Fetch Tfl predictions tflPredictions := performanceeval.FetchTflJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes) /* Cache for real time estimates which can be reused for different prediction methods map (fromStop => (toStop => (departureTime => "estimate"/"stddev" => int in seconds)))) */ realTimeEstimatesCache := make(map[string]map[string]map[time.Time]map[string]int64) // Generate predictions using our methods predictions := make(map[performanceeval.Method]map[time.Time]time.Duration) for _, method := range performanceeval.Methods() { predictions[method] = performanceeval.PredictJourneyTimes(line, bound, fromStop, toStop, actualDepartureTimes, method, realTimeEstimatesCache) } // Convert the above into PerformanceTestSeries2 actualJourneyTimesSeries := PerformanceTestSeries2{ SeriesName: "Actual Time", X: actualDepartureTimes, Y: actualJourneyTimeDurations, Count: len(actualJourneyTimeDurations), } sort.Sort(actualJourneyTimesSeries) tflPredictionsSeries := mapToPerformanceTestSeries2("Tfl Expected Time", tflPredictions, actualJourneyTimes) sort.Sort(tflPredictionsSeries) performanceTestSeries2 := []PerformanceTestSeries2{ actualJourneyTimesSeries, tflPredictionsSeries, } // Add all prediction series into performance test series 2 for method, predictedJouneyTimes := range predictions { predictionSeries := mapToPerformanceTestSeries2(method.String(), predictedJouneyTimes, actualJourneyTimes) sort.Sort(predictionSeries) performanceTestSeries2 = append(performanceTestSeries2, predictionSeries) } charts = append(charts, PerformanceChart2{ Line: line, Bound: bound, Origin: fromStop, Destination: toStop, OriginName: fromStopName, DestinationName: toStopName, MultipleSeries: performanceTestSeries2, ServerDomain: credentials.GetGoServerDomain(), }) logger.GetLogger().Info("Done for line: %v, bound: %v, fromStop: %v, toStop: %v", line, bound, fromStop, toStop) } summary := PerformancePlotSummary2{ Methods: performanceeval.Methods(), Charts: charts, } writePerformanceChart2(wr, summary, templatePath) }