func getRoutePaths(linesToPlot map[string]map[string]map[string]string) map[string][]LatLng {
	routePaths := make(map[string][]LatLng)
	bound := "outbound"

	for line, lineDetails := range linesToPlot {
		fromStop := lineDetails[bound]["fromStop"]
		toStop := lineDetails[bound]["toStop"]
		intermediateStops := selectdb.SelectIntermediateStops(line, bound, fromStop, toStop)

		path := make([]LatLng, len(intermediateStops))

		intermediateStopDetails := selectdb.SelectMultipleStops(intermediateStops)
		for i, stop := range intermediateStops {
			lat, err := strconv.ParseFloat(intermediateStopDetails[stop]["latitude"], 64)
			if err != nil {
				logger.GetLogger().Panic(err)
			}

			lng, err := strconv.ParseFloat(intermediateStopDetails[stop]["longitude"], 64)
			if err != nil {
				logger.GetLogger().Panic(err)
			}

			path[i] = LatLng{
				Lat: lat,
				Lng: lng,
			}
		}
		routePaths[line] = path
	}
	return routePaths
}
func getIcsCodes(lineBoundsStops map[string]map[string]string) map[string]map[string]string {
	stopsMap := make(map[string]bool)
	for _, lineDetails := range lineBoundsStops {
		for _, naptanId := range lineDetails {
			stopsMap[naptanId] = true
		}
	}

	stops := make([]string, len(stopsMap))
	counter := 0
	for stop, _ := range stopsMap {
		stops[counter] = stop
		counter++
	}
	stopInfos := selectdb.SelectMultipleStops(stops)

	icsCodes := make(map[string]map[string]string)
	for line, lineDetails := range lineBoundsStops {
		for bound, naptanId := range lineDetails {
			_, exists := icsCodes[line]
			if !exists {
				icsCodes[line] = make(map[string]string)
			}
			icsCodes[line][bound] = stopInfos[naptanId]["icscode"]
		}
	}
	return icsCodes
}
func getStopInfos(lineStopPointsByRoute map[string]map[string][]string) map[string]map[string][]TflStopPoint {
	/* Get slice of all stops */
	stopsMap := make(map[string]bool)
	for _, lineDetails := range lineStopPointsByRoute {
		for _, orderedStopPoints := range lineDetails {
			for _, stop := range orderedStopPoints {
				stopsMap[stop] = true
			}
		}
	}
	stops := make([]string, len(stopsMap))
	counter := 0
	for stop, _ := range stopsMap {
		stops[counter] = stop
		counter++
	}

	stopInfos := selectdb.SelectMultipleStops(stops)

	result := make(map[string]map[string][]TflStopPoint)
	for line, lineDetails := range lineStopPointsByRoute {
		for bound, orderedStopPoints := range lineDetails {
			for _, naptanId := range orderedStopPoints {
				_, exists := result[line]
				if !exists {
					result[line] = make(map[string][]TflStopPoint)
				}
				_, exists = result[line][bound]
				if !exists {
					result[line][bound] = make([]TflStopPoint, 0)
				}

				stopInfo := stopInfos[naptanId]
				name := stopInfo["name"]
				latitude, err := strconv.ParseFloat(stopInfo["latitude"], 64)
				if err != nil {
					logger.GetLogger().Panic(err)
				}
				longitude, err := strconv.ParseFloat(stopInfo["longitude"], 64)
				if err != nil {
					logger.GetLogger().Panic(err)
				}

				result[line][bound] = append(result[line][bound],
					TflStopPoint{
						Name:      name,
						NaptanId:  naptanId,
						Latitude:  latitude,
						Longitude: longitude,
					})
			}
		}
	}
	return result
}
/* Returns map from naptan ids to latitudes and longitudes
   map (naptanId =>
   		("latitude" => latitude
   		 "longitude" => longitude
   		 )) */
func getLatLngs(naptanIds []string) map[string]map[string]float64 {
	stops := selectdb.SelectMultipleStops(naptanIds)

	result := make(map[string]map[string]float64)
	for _, stop := range stops {
		naptanId := stop["id"]
		latitude, err := strconv.ParseFloat(stop["latitude"], 64)
		if err != nil {
			logger.GetLogger().Panic(err)
		}
		longitude, err := strconv.ParseFloat(stop["longitude"], 64)
		if err != nil {
			logger.GetLogger().Panic(err)
		}
		result[naptanId] = map[string]float64{
			"latitude":  latitude,
			"longitude": longitude,
		}
	}
	return result
}
/* Returns the stop details for each delayed section
   map (naptanId
		 => ("name" => name
		     "latitude" => lat
		     "longitude" => lng
		     ...))) */
func extractAllStopDetailsFromDelays(delays map[string]map[string]map[string]int64) map[string]map[string]string {
	if len(delays) == 0 {
		return nil
	}

	stopsMap := make(map[string]bool)
	for fromStop, fromStopDetails := range delays {
		for toStop, _ := range fromStopDetails {
			stopsMap[fromStop] = true
			stopsMap[toStop] = true
		}
	}

	stops := make([]string, len(stopsMap))
	counter := 0
	for stop, _ := range stopsMap {
		stops[counter] = stop
		counter++
	}
	return selectdb.SelectMultipleStops(stops)
}
/* Change the predictions inside directions to use our own predictions */
func (directions *TflDirections) UpdatePredictions(isDeparting bool, departureOrArrivalTime time.Time) error {
	journeys := &directions.Journeys
	for i := 0; i < len(*journeys); i++ {
		/* Counter for keeping track of times for prediction */
		cumulativeTime := departureOrArrivalTime
		if !isDeparting {
			/* Approximate departure time for arriving case by subtracting tfl estimate */
			cumulativeTime = departureOrArrivalTime.Add(-time.Duration((*journeys)[i].TflDuration) * time.Minute)
		}
		initialTime := cumulativeTime

		legs := &(*journeys)[i].Legs
		for j := 0; j < len(*legs); j++ {
			leg := &(*legs)[j]
			leg.DepartureTime = formatISOTime(cumulativeTime)
			if leg.Mode.Id == "bus" {
				var line string
				var bound string
				var stopPoints []string

				lineBoundCombination := make(map[string]string)

				/* Update naptan ids and coordinates of intermediate stops */
				for i, routeOption := range leg.RouteOptions {
					var err error
					line = routeOption.LineIdentifier.Id
					stopPoints, bound, err = searchIntermediateStops(line, leg.DeparturePoint.IcsCode, leg.ArrivalPoint.IcsCode)

					if err != nil {
						/* Bus may not be Tfl operated (e.g. bus 555 to Heathrow). In such case skip. */
						continue
					}

					/* Add bound to struct */
					leg.RouteOptions[i].LineIdentifier.Bound = bound
					lineBoundCombination[line] = bound
				}

				/* If no stop points found, invalidate journey for removal afterwards*/
				if len(stopPoints) == 0 {
					(*journeys)[i].IsInvalid = true
					continue
				}

				leg.IntermediateStops = make([]TflStopPoint, len(stopPoints))

				details := selectdb.SelectMultipleStops(stopPoints)
				for i, naptanId := range stopPoints {
					latitude, _ := strconv.ParseFloat(details[naptanId]["latitude"], 64)
					longitude, _ := strconv.ParseFloat(details[naptanId]["longitude"], 64)
					leg.IntermediateStops[i] = TflStopPoint{
						Name:      details[naptanId]["name"],
						NaptanId:  naptanId,
						Latitude:  latitude,
						Longitude: longitude,
					}
				}

				/* Update bus prediction */
				newPrediction, historicBasedPrediction, realTimeBasedPrediction, newPredictionAdjacentStops, err := updateBusPrediction(line, bound, stopPoints, cumulativeTime)
				if err == nil {
					leg.DurationInMinutes = int(newPrediction.Minutes())
					leg.HistoricDurationInMinutes = int(historicBasedPrediction.Minutes())
					leg.RealTimeDurationInMinutes = int(realTimeBasedPrediction.Minutes())
				}

				/* Update next bus arrivals */
				fromStop := leg.IntermediateStops[0].NaptanId
				nextBuses := selectdb.SelectNextBuses(lineBoundCombination, fromStop, time.Now().In(timezone.Get()))
				leg.NextBuses = make([]TflNextBus, len(nextBuses))
				for i, nextBus := range nextBuses {
					leg.NextBuses[i] = TflNextBus{
						Line:                nextBus.Line,
						Bound:               nextBus.Bound,
						VehicleId:           nextBus.VehicleId,
						ExpectedArrivalTime: formatISOTime(nextBus.ExpectedArrivalTime),
						AlreadyDeparted:     nextBus.AlreadyDeparted,
					}
				}
				addToNextBusesCurrentLocations(&leg.NextBuses)

				/* Find out next bus frequency */
				averageWaitTime, minWaitTime, maxWaitTime, err := selectdb.SelectAverageWaitTimes(lineBoundCombination, initialTime)
				if err != nil {
					logger.GetLogger().Error(err.Error())
				}
				leg.FrequencyLow = int(minWaitTime.Minutes())
				leg.FrequencyHigh = int(maxWaitTime.Minutes())

				nextBusFlag := false
				if len(nextBuses) > 0 {
					/* Find first bus that is going to arrive and the user is able to catch */
					for _, nextBus := range nextBuses {
						nextBusTime := nextBus.ExpectedArrivalTime
						if nextBusTime.After(cumulativeTime) {
							leg.TimeToNextBus = int(nextBusTime.Sub(cumulativeTime).Minutes())
							cumulativeTime = nextBusTime
							nextBusFlag = true
							break
						}
					}
				}
				if len(nextBuses) == 0 || (len(nextBuses) > 0 && !nextBusFlag) {
					/* Handle special case if lack of next buses, use average wait times instead */
					leg.TimeToNextBus = int(averageWaitTime.Minutes())
					cumulativeTime = cumulativeTime.Add(averageWaitTime)
				}

				/* Update cumulative time */
				leg.IntermediateStops[0].ExpectedArrival = int(cumulativeTime.Sub(initialTime).Minutes())
				for i := 0; i < len(stopPoints)-1; i++ {
					fromStop := stopPoints[i]
					toStop := stopPoints[i+1]
					cumulativeTime = cumulativeTime.Add(newPredictionAdjacentStops[fromStop][toStop])
					leg.IntermediateStops[i+1].ExpectedArrival = int(cumulativeTime.Sub(initialTime).Minutes())
				}
			} else {
				cumulativeTime = cumulativeTime.Add(time.Duration(leg.DurationInMinutes) * time.Minute)
			}
		}
	}

	/* Remove invalid journeys */
	for i := len(*journeys) - 1; i >= 0; i-- {
		if (*journeys)[i].IsInvalid {
			*journeys = append((*journeys)[:i], (*journeys)[i+1:]...)
		}
	}
	return nil
}