/* Function to synchronise with tfl - blocks here until tfl site is first refreshed Use with caution as tfl doesn't seem to adhere strictly to update interval */ func SynchroniseWithTflArrivals(line string) { site := fmt.Sprintf("https://api.tfl.gov.uk/Line/%v/Arrivals?", line) var oldBody []byte for range time.Tick(time.Second) { body := webconn.GetBodyFromTfl(site) if len(oldBody) > 0 && !bytes.Equal(oldBody, body) { logger.GetLogger().Info("Synchronised with tfl arrivals for line %v", line) return } oldBody = body } }
/* Returns map from naptan_id to ics code. Error is returned if failure to obtain ics code for any stop */ func GetIcsCodes(naptanIds []string) (map[string]string, error) { site := fmt.Sprintf("https://api.tfl.gov.uk/StopPoint/%v?", strings.Join(naptanIds, ",")) body := webconn.GetBodyFromTfl(site) type StopPoint struct { Children []json.RawMessage `json:"children"` } var stopPoints []StopPoint err := json.Unmarshal(body, &stopPoints) /* Sometimes Tfl returns "Technical error". In such case we wait a few seconds and retry */ if err != nil { time.Sleep(10 * time.Second) return GetIcsCodes(naptanIds) } icsCodes := make(map[string]string) /* Convert naptanIds to a map for easy membership testing later on */ naptanIdsMap := make(map[string]bool) for _, naptanId := range naptanIds { naptanIdsMap[naptanId] = true } for _, stopPoint := range stopPoints { for _, child := range stopPoint.Children { /* Find out which naptanId from our slice does this stop point correspond to (Recursion may be required) If fail skip and continue*/ naptanId, ics, err := recurseAndGetChildIcs(&child, naptanIdsMap) if err == nil { icsCodes[naptanId] = ics break } } } /* Final check: make sure all naptan ids in the original request are covered */ for _, naptanId := range naptanIds { _, exists := icsCodes[naptanId] if !exists { return nil, errors.New( fmt.Sprintf("Failed to find ics code for stop %v", naptanId)) } } return icsCodes, nil }
func getDirectionsFromTfl(fromLat, fromLng, toLat, toLng string, isDeparting bool, targetTime time.Time) TflDirections { /* Configure parameters to fit URL */ timeIsStr := "departing" if !isDeparting { timeIsStr = "arriving" } dateStrFormat := "20060102" dateStr := targetTime.Format(dateStrFormat) timeStrFormat := "1504" timeStr := targetTime.Format(timeStrFormat) site := fmt.Sprintf("https://api.tfl.gov.uk/Journey/JourneyResults/%v,%v/to/%v,%v?timeIs=%v&mode=bus&date=%v&time=%v", fromLat, fromLng, toLat, toLng, timeIsStr, dateStr, timeStr) return parseDirectionsFromBody(webconn.GetBodyFromTfl(site)) }
/* Returns whether there was a bus that departed, and if yes, its registration number. Also new departure schedule. Time of departure. Error if no service operating at that time e.g. night buses site - tfl API website to query from to obtain json response body departureSchedule - map from vehicle id to expected departure time */ func monitorForDeparture(site string, departureSchedule map[string]string, line, bound string) (bool, string, map[string]string, time.Time, error) { body := webconn.GetBodyFromTfl(site) type BusInfo struct { VehicleId string `json:"vehicleId"` ExpectedDeparture string `json:"expectedArrival"` } var busInfo []BusInfo err := json.Unmarshal(body, &busInfo) /* Sometimes Tfl returns "Technical error". In such case we retry after a few seconds */ if err != nil { logger.GetLogger().Error("Tfl failed to give proper arrivals response: %v", string(body)) time.Sleep(10 * time.Second) return monitorForDeparture(site, departureSchedule, line, bound) } if len(busInfo) == 0 { return false, "", nil, time.Time{}, errors.New( fmt.Sprintf("No bus operating for line %v (%v) now", line, bound)) } newDepartureSchedule := make(map[string]string) for _, bus := range busInfo { newDepartureSchedule[bus.VehicleId] = bus.ExpectedDeparture } for vehicleId, _ := range departureSchedule { _, exists := newDepartureSchedule[vehicleId] if !exists { departureTime := departureSchedule[vehicleId] if timeStampHasPassed(departureTime) { return true, vehicleId, nil, parseTflTimeStamp(departureTime), nil } else { return false, "", departureSchedule, time.Time{}, nil } } } return false, "", newDepartureSchedule, time.Time{}, nil }
/* Returns map of journey times (in minutes) (to_id => journey time between from_id and to_id) */ func getJourneyTimes(line, bound, startingPoint string) map[string]int { site := fmt.Sprintf("https://api.tfl.gov.uk/Line/%v/Timetable/%v?direction=%v", line, startingPoint, bound) cumulativeJourneyTimes := parseCumulativeJourneyTimesFromBody(line, bound, webconn.GetBodyFromTfl(site)) journeyTimes := make(map[string]int) for i, m := range cumulativeJourneyTimes { for to_id, cumTime := range m { if i > 0 { prevMap := cumulativeJourneyTimes[i-1] for _, prevCumTime := range prevMap { cumTime = cumTime - prevCumTime } } journeyTimes[to_id] = cumTime } } return journeyTimes }
func getNewStopInfos(stops []string) map[string]map[string]string { commaDelimitedStops := strings.Join(stops, ",") site := fmt.Sprintf("https://api.tfl.gov.uk/StopPoint/%v?", commaDelimitedStops) /* Create map out of stops for fast lookup later */ stopsMap := make(map[string]bool) for _, v := range stops { stopsMap[v] = true } // Retry if got 'technical error' from tfl var stopInfos map[string]map[string]string var err error for ok := true; ok; ok = (err != nil) { stopInfos, err = parseStopsFromBody(webconn.GetBodyFromTfl(site), stopsMap) } return stopInfos }
/* Returns whether the bus arrived, and if yes, its time of arrival. Also new departure schedule site - tfl API website to query from to obtain json response body vehicleId - the vehicle id wait for until appearance and disappearance departureSchedule - map from vehicle id to expected departure time */ func monitorForArrival(site, vehicleId string, departureSchedule map[string]string) (bool, time.Time, map[string]string) { body := webconn.GetBodyFromTfl(site) type BusInfo struct { VehicleId string `json:"vehicleId"` ExpectedDeparture string `json:"expectedArrival"` } var busInfo []BusInfo err := json.Unmarshal(body, &busInfo) /* 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)) time.Sleep(10 * time.Second) return monitorForArrival(site, vehicleId, departureSchedule) } newDepartureSchedule := make(map[string]string) for _, bus := range busInfo { if bus.VehicleId == vehicleId { newDepartureSchedule[bus.VehicleId] = bus.ExpectedDeparture } } // If target bus has just disappeared arrivals board, it is assumed arrived expectedDeparture, existsOnPreviousBoard := departureSchedule[vehicleId] _, existsOnCurrentBoard := newDepartureSchedule[vehicleId] if existsOnPreviousBoard && !existsOnCurrentBoard { if timeStampHasPassed(expectedDeparture) { return true, parseTflTimeStamp(expectedDeparture), nil } else { return false, time.Time{}, departureSchedule } } return false, time.Time{}, newDepartureSchedule }
/* Returns the expected journey time (in minutes) returned from tfl journey planner */ func getTflExpectedJourneyTime(line, bound, origin, destination, vehicleId string) (time.Duration, error) { icsCodes, err := geography.GetIcsCodes([]string{origin, destination}) if err != nil { logger.GetLogger().Panic(err) } site := fmt.Sprintf("https://api.tfl.gov.uk/Journey/JourneyResults/%v/to/%v?mode=bus&journeyPreference=leastinterchange", icsCodes[origin], icsCodes[destination]) body := webconn.GetBodyFromTfl(site) type JourneyResult struct { Journeys []json.RawMessage `json:"journeys"` } var journeyResult JourneyResult err = json.Unmarshal(body, &journeyResult) /* Sometimes Tfl returns "Technical error". In such case we retry after a few seconds */ if err != nil { time.Sleep(10 * time.Second) return getTflExpectedJourneyTime(line, bound, origin, destination, vehicleId) } // Assume the first journey in the results are the one we want if len(journeyResult.Journeys) == 0 { return 0, errors.New( fmt.Sprintf("No journey found for line %v (%v) %v -> %v. Aborting", line, bound, origin, destination)) } for _, potentialJourney := range journeyResult.Journeys { journeyTime, err := extractTimeOnBusFromJourney(&potentialJourney, line, bound, origin, destination) if err == nil { return time.Duration(journeyTime) * time.Minute, nil } } return 0, err }
func pollTflArrivals(line string, cache map[string]map[string]string) { site := fmt.Sprintf("https://api.tfl.gov.uk/Line/%v/Arrivals?", line) arrivalsBundle := parseArrivalsFromBody(webconn.GetBodyFromTfl(site)) updateCacheDBWithArrivalsBundle(line, arrivalsBundle, cache, nil) }
func pollAllTflArrivals(lines map[string]bool, numPredictionsPerStop *int, arrivalsChannels map[string]chan map[string]map[string]string) { site := fmt.Sprintf("https://api.tfl.gov.uk/Mode/bus/Arrivals?count=%v", *numPredictionsPerStop) parseAllArrivalsFromBody(webconn.GetBodyFromTfl(site), arrivalsChannels) }
func pollAllTflTerminusDepartures(numPredictionsPerStop *int) { site := fmt.Sprintf("https://api.tfl.gov.uk/Mode/bus/Arrivals?count=%v", *numPredictionsPerStop) parseAllTerminusDeparturesFromBody(webconn.GetBodyFromTfl(site), numPredictionsPerStop) }
/* Returns 2 maps, the first from naptan_id to longitude, and the second from naptan_id to latitude. Error is returned if failure to obtain coordinates for any stop */ func GetLongLat(naptanIds []string) (map[string]float64, map[string]float64, error) { site := fmt.Sprintf("https://api.tfl.gov.uk/StopPoint/%v?", strings.Join(naptanIds, ",")) body := webconn.GetBodyFromTfl(site) type LineGroup struct { NaptanIdReference string `json:"naptanIdReference"` } type StopPoint struct { CommonName string `json:"commonName"` Longitude float64 `json:"lon"` Latitude float64 `json:"lat"` LineGroup []LineGroup } var stopPoints []StopPoint err := json.Unmarshal(body, &stopPoints) /* Sometimes Tfl returns "Technical error". In such case we wait a few seconds and retry */ if err != nil { time.Sleep(10 * time.Second) return GetLongLat(naptanIds) } longitudes := make(map[string]float64) latitudes := make(map[string]float64) /* Convert naptanIds to a map for easy membership testing later on */ naptanIdsMap := make(map[string]bool) for _, naptanId := range naptanIds { naptanIdsMap[naptanId] = true } for _, stopPoint := range stopPoints { /* Find out which naptanId from our slice does this stop point correspond to If fail skip and continue*/ naptanId := "" for _, lineGroup := range stopPoint.LineGroup { if naptanIdsMap[lineGroup.NaptanIdReference] { naptanId = lineGroup.NaptanIdReference break } } if naptanId == "" { logger.GetLogger().Error("Failed to find original naptan id for stop %v", stopPoint.CommonName) continue } longitudes[naptanId] = stopPoint.Longitude latitudes[naptanId] = stopPoint.Latitude } /* Final check: make sure all naptan ids in the original request are covered */ for _, naptanId := range naptanIds { _, longitudeExists := longitudes[naptanId] _, latitudeExists := latitudes[naptanId] if !longitudeExists || !latitudeExists { return nil, nil, errors.New( fmt.Sprintf("Failed to find longitude/latitude for stop %v", naptanId)) } } return longitudes, latitudes, nil }
func getNewLineStopPoints(line, bound string) ([]string, map[string]map[string]string) { site := fmt.Sprintf("https://api.tfl.gov.uk/Line/%v/Route/Sequence/%v?", line, bound) return parseLineStopPointsFromBody(webconn.GetBodyFromTfl(site)) }
func getTflEstimate(line, bound, originIcs, destIcs string) time.Duration { site := fmt.Sprintf("https://api.tfl.gov.uk/Journey/JourneyResults/%v/to/%v?mode=bus&journeyPreference=leastinterchange", originIcs, destIcs) return parseTflEstimateFromBody(line, webconn.GetBodyFromTfl(site)) }
func getNewLines() mapset.Set { site := "https://api.tfl.gov.uk/Line/Route?" return mapset.NewSetFromSlice(parseLinesFromBody(webconn.GetBodyFromTfl(site))) }
func getNewTimetable(line, bound, stationNaptan string) (map[time.Weekday]map[selectdb.LocalTime]bool, error) { site := fmt.Sprintf("https://api.tfl.gov.uk/Line/%v/Timetable/%v?direction=%v", line, stationNaptan, bound) return parseTimetableFromBody(webconn.GetBodyFromTfl(site), line, bound, stationNaptan) }