/* Inserts into channel arrivalsBundle once data is received from tfl and parsed map (vehicleId => ("line" => line, "bound" => bound, "naptanId" => naptanId of terminus, "expectedDeparture" => expectedDeparture in TfL format, )) */ func parseAllTerminusDeparturesFromBody(body []byte, numPredictionsPerStop *int) { type Arrivals struct { LineId string `json:"lineId"` VehicleId string `json:"vehicleId"` Bound string `json:"direction"` NaptanId string `json:"naptanId"` TimeStamp string `json:"timeStamp"` ExpectedDeparture string `json:"expectedArrival"` } var arrivals []Arrivals err := json.Unmarshal(body, &arrivals) /* Sometimes Tfl returns "Technical error". In such case we skip and return gracefully */ if err != nil { logger.GetLogger().Error("Tfl failed to give proper arrivals response: %v", string(body)) return } /* Select all departing terminuses of all lines map (line => (bound => naptanId)) */ interestedTerminuses := selectdb.SelectAllOrigins() result := make(map[string]map[string]string) for _, bus := range arrivals { /* Check that incoming prediction is not in the past, just in case (n.b. allow 3 min as buffer due to heavy cpu usage causing delay in processing data) */ if timeStampHasPassedBy(bus.ExpectedDeparture, 5*time.Minute) { // Temporary fix -- sometimes webconn stops unexpectedly, works on restart of program // Change (to a new) numPredictionsPerStop to hopefully remedy the issue logger.GetLogger().Error("Expected arrival %v of bus %v: %v (%v) is in the past, skipped.", bus.ExpectedDeparture, bus.VehicleId, bus.LineId, bus.Bound) for { newNumPredictionsPerStop := getNumPredictionsPerStop() if *numPredictionsPerStop != newNumPredictionsPerStop { logger.GetLogger().Info("Changed numPredictionsPerStop from %v to %v", *numPredictionsPerStop, newNumPredictionsPerStop) *numPredictionsPerStop = newNumPredictionsPerStop break } } logger.UseNewLogFile() return } /* Check if prediction refers to a departing terminus */ if bus.NaptanId == interestedTerminuses[bus.LineId][bus.Bound] { result[bus.VehicleId] = map[string]string{ "line": bus.LineId, "bound": bus.Bound, "naptanId": bus.NaptanId, "expectedDeparture": bus.ExpectedDeparture, } } } insertExpectedDeparturesIntoDB(result) }
/* Checks whether bus is still serving in same route and direction. If so, update database and cache. Otherwise, only update cache. */ func updateCacheDBWithArrivalsBundle(line string, arrivalsBundle, cache map[string]map[string]string, numPredictionsPerStop *int) { // For logging purpose newBuses := make([]string, 0) arrivedStop := make([]string, 0) withdrawnBuses := make([]string, 0) // For updating database later below values := make([]string, 0) skippedStopsValues := make([]string, 0) // For debugging purpose - store all those original (raw) times that are interpolated to zero, say due to // backwards in time rawArrivalTimes := make([]string, 0) // TAKEN OUT FOR NOW SINCE BUS PREDICTIONS MAY TEMPORARILY DISAPPEAR - NOT RELIABLE // Remove withdrawn buses //for vehicleId, _ := range cache { // /* If bus found in cache, but not in arrivalsBundle, remove from cache. */ // if arrivalsBundle[vehicleId] == nil { // withdrawnBuses = append(withdrawnBuses, vehicleId) // delete(cache, vehicleId) // } //} for vehicleId, bundle := range arrivalsBundle { /* If bus not found in cache, add to cache */ if cache[vehicleId] == nil { cache[vehicleId] = createNewCacheEntry(bundle) newBuses = append(newBuses, vehicleId) continue } cachedEntry := cache[vehicleId] cachedDirection := cachedEntry["direction"] cachedFromStop := cachedEntry["fromStop"] cachedFromStopExpectedArrival := cachedEntry["fromStopExpectedArrival"] cachedFromStopTflTimestamp := cachedEntry["fromStopTflTimestamp"] cachedToStop := cachedEntry["toStop"] cachedToStopExpectedArrival := cachedEntry["toStopExpectedArrival"] cachedToStopTflTimestamp := cachedEntry["toStopTflTimestamp"] /* Discard incoming prediction if in the past, just in case (n.b. allow 3 min as buffer due to heavy cpu usage causing delay in processing data) */ if timeStampHasPassedBy(bundle["expectedArrival"], 3*time.Minute) { /*logger.GetLogger().Error("Expected arrival %v of bus %v: %v (%v) is in the past, skipped.", bundle["expectedArrival"], vehicleId, line, bundle["direction"]) continue*/ // Temporary fix -- sometimes webconn stops unexpectedly, works on restart of program // Change (to a new) numPredictionsPerStop to hopefully remedy the issue logger.GetLogger().Error("Expected arrival %v of bus %v: %v (%v) is in the past, skipped.", bundle["expectedArrival"], vehicleId, line, bundle["direction"]) for { newNumPredictionsPerStop := getNumPredictionsPerStop() if *numPredictionsPerStop != newNumPredictionsPerStop { logger.GetLogger().Info("Changed numPredictionsPerStop from %v to %v", *numPredictionsPerStop, newNumPredictionsPerStop) *numPredictionsPerStop = newNumPredictionsPerStop break } } logger.UseNewLogFile() return } // Handle first time case where fromStop is not yet present in cache if cachedFromStopExpectedArrival == "" { if !timeStampHasPassed(cachedToStopExpectedArrival) { originalToStopSeq, err := selectdb.SelectStopSeq(line, cachedDirection, cachedToStop) newToStopSeq, err2 := selectdb.SelectStopSeq(line, cachedDirection, bundle["naptanId"]) if err != nil || err2 != nil { /* Stop may not be well formed, i.e. not on line and direction Bus could have changed direction / terminate early In such cases, safest to delete from cache and start all over again for this vehicle */ delete(cache, vehicleId) continue } if newToStopSeq <= originalToStopSeq { /* Ensure we are not accidentally advancing bus if departure time has not passed. This is because predictions can temporarily disappear, leading to fluctuations in between neighbouring stops even when it is not yet time */ cache[vehicleId] = updateCacheEntry(cachedEntry, bundle) } } else { cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle) } continue } /* If current prediction points to stop at or before cached prediction, update it, since it is guaranteed to be in the future if reached here. */ fromStopSeq, err := selectdb.SelectStopSeq(line, cachedDirection, cachedFromStop) toStopSeq, err2 := selectdb.SelectStopSeq(line, cachedDirection, cachedToStop) if err != nil || err2 != nil { /* Stop may not be well formed, i.e. not on line and direction Bus could have changed direction / terminate early (if changed direction proceed with interpolation) In such cases, safest to delete from cache and start all over again for this vehicle */ if err == nil { interpolateIfChangedDirection(line, cachedDirection, vehicleId, cachedFromStop, cachedToStop, cachedFromStopExpectedArrival, cachedToStopExpectedArrival, cachedFromStopTflTimestamp, cachedToStopTflTimestamp, &values, &skippedStopsValues, &rawArrivalTimes, cachedEntry) } delete(cache, vehicleId) continue } if toStopSeq <= fromStopSeq { cache[vehicleId] = updateCacheEntry(cachedEntry, bundle) continue } /* Otherwise current prediction points to future stop, update only if timestamp of current prediction has expired, so that bus has left station. And that current prediction refers to a valid time after the cached prediction */ if !timeStampHasPassed(cachedToStopExpectedArrival) || timeDifference(cachedToStopExpectedArrival, bundle["expectedArrival"]) <= 0 { continue } /* For some random reason expected arrival of to stop happens to occur before from stop. So, check that to stop occurs after from stop. If not, discard. */ /* Also check that journey time is not erroneously large (e.g. >= 2 hours), if so, discard */ twoHours := 7200 timeDiff := timeDifference(cachedFromStopExpectedArrival, cachedToStopExpectedArrival) if timeDiff <= 0 || timeDiff >= twoHours { cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle) continue } /* Check whether bus has skipped stations, activiate interpolation if required */ if toStopSeq > fromStopSeq+1 { logger.GetLogger().Info("Bus %v of line %v (%v) has skipped stations. From stop: %v. To stop: %v", vehicleId, line, cachedDirection, cachedFromStop, cachedToStop) cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle) arrivedStop = append(arrivedStop, vehicleId) /* Add to database using interpolation method */ interpolateStops(line, cachedDirection, vehicleId, cachedFromStop, cachedToStop, cachedFromStopExpectedArrival, cachedToStopExpectedArrival, cachedFromStopTflTimestamp, cachedToStopTflTimestamp, &values, &skippedStopsValues, &rawArrivalTimes, cachedEntry) continue } values = append(values, fmt.Sprintf("('%v', '%v', '%v', '%v', TIMESTAMP '%v', TIMESTAMP '%v', "+ "'%v', TIMESTAMP '%v', TIMESTAMP '%v', INTERVAL '%v seconds', FALSE)", line, cachedDirection, vehicleId, cachedFromStop, toPsqlTimeStamp(cachedFromStopExpectedArrival), toPsqlTimeStamp(cachedFromStopTflTimestamp), cachedToStop, toPsqlTimeStamp(cachedToStopExpectedArrival), toPsqlTimeStamp(cachedToStopTflTimestamp), timeDiff)) cache[vehicleId] = shiftCacheEntry(cachedEntry, bundle) arrivedStop = append(arrivedStop, vehicleId) } // Log information logger.GetLogger().Info("New buses added to cache of line %v: %v", line, newBuses) logger.GetLogger().Info("Buses arrived next stop for line %v: %v", line, arrivedStop) logger.GetLogger().Info("Buses withdrawn from line %v: %v", line, withdrawnBuses) // If nothing to update in database, return quietly if len(values) == 0 { return } dbconn.AcquireLock() logger.GetLogger().Info("INSERT INTO journey_history (line, bound, vehicle_id, "+ "from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+ "time_taken, interpolated) VALUES %s", strings.Join(values, ",")) _, err := db.Exec(fmt.Sprintf("INSERT INTO journey_history (line, bound, vehicle_id, "+ "from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+ "time_taken, interpolated) VALUES %s", strings.Join(values, ","))) dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } /* // If no bus skipped stops, skip quietly if len(skippedStopsValues) > 0 { dbconn.AcquireLock() _, err = db.Exec( fmt.Sprintf("INSERT INTO skipped_stops_history (line, bound, vehicle_id, "+ "from_id, from_timestamp, from_timestamp_tfl, to_id, to_timestamp, to_timestamp_tfl, "+ "stops_in_between, num_stops_in_between) VALUES %s", strings.Join(skippedStopsValues, ","))) dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } } // If no raw arrival times, skip quietly if len(rawArrivalTimes) > 0 { logger.GetLogger().Info( fmt.Sprintf("INSERT INTO raw_arrival_times (line, bound, vehicle_id, naptan_id, timestamp) VALUES %s", strings.Join(rawArrivalTimes, ","))) dbconn.AcquireLock() _, err = db.Exec( fmt.Sprintf("INSERT INTO raw_arrival_times (line, bound, vehicle_id, naptan_id, timestamp) VALUES %s", strings.Join(rawArrivalTimes, ","))) dbconn.ReleaseLock() if err != nil { logger.GetLogger().Panic(err) } }*/ }