Last active
December 7, 2018 15:01
-
-
Save mobileappconsultant/fabd3aaa01337ee082de5249f526e523 to your computer and use it in GitHub Desktop.
Python app to allow a user plan their journey from start to destination using Transport For London Live Service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import requests | |
import re | |
import json | |
import sys | |
import time | |
##### IF YOU ARE USING COMMAND LINE TO RUN THIS PLS DO pip install requests re json sys time | |
# get current postcode from user -> DONE | |
# get destination postcode from user -> DONE | |
# check it meets the required format -> DONE | |
# complete the url with placeholders -> DONE | |
# submit request -> DONE | |
# process response check for errors -> DONE | |
# process response to display data -> DONE | |
tfl_provided_app_id = "99acf05e" | |
tfl_provided_app_key = "a5d066ef8faf90351c9cc1ff438648e3" | |
CONSTANT_REGULAR_EXPRESSION_FOR_POSTCODE = "([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?))))\s?[0-9][A-Za-z]{2})" | |
CONSTANT_BASE_URL_FOR_TRANSPORT_FOR_LONDON = "https://api.tfl.gov.uk/journey/journeyresults/{}/to/{}?app_id={}&app_key={}" | |
CONSTANT_JOURNEYS = "journeys" | |
CONSTANT_DURATION = "duration" | |
CONSTANT_LEGS = "legs" | |
CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR = 60 | |
CONSTANT_FARE = "fare" | |
CONSTANT_TOTAL_COST = "totalCost" | |
CONSTANT_FARES = "fares" | |
CONSTANT_INSTRUCTION = "instruction" | |
CONSTANT_DETAILED = "detailed" | |
CONSTANT_MODE = "mode" | |
CONSTANT_NAME = "name" | |
input_user_start_postcode = "" | |
input_user_end_postcode = "" | |
def getTravelInstructionsFromUser(): | |
input_user_start_postcode = input("\nPlease enter the postcode of where you are starting your journey: ") | |
input_user_end_postcode = input("\nPlease enter the postcode of where you are going: ") | |
return [input_user_start_postcode, input_user_end_postcode] | |
def validateUserEnteredInstructions(travel_instructions): | |
print(travel_instructions[0]) | |
print(travel_instructions[1]) | |
return re.search(CONSTANT_REGULAR_EXPRESSION_FOR_POSTCODE, travel_instructions[0]) and re.search( | |
CONSTANT_REGULAR_EXPRESSION_FOR_POSTCODE, | |
travel_instructions[1]) | |
def completeTheUrlForTheRequestToTheServer(travel_instructions): | |
return CONSTANT_BASE_URL_FOR_TRANSPORT_FOR_LONDON.format(travel_instructions[0], travel_instructions[1], | |
tfl_provided_app_id, tfl_provided_app_key).replace(" ", "") | |
def getTimeInMinutesAndSections(data, journeyIndex): | |
journeys = data[CONSTANT_JOURNEYS] | |
print(journeys[journeyIndex][CONSTANT_DURATION]) | |
def getAverageTravelTime(data): | |
journeys = data[CONSTANT_JOURNEYS] | |
totalTime = 0 | |
for journeyIndex in range(len(journeys)): | |
totalTime += journeys[journeyIndex][CONSTANT_DURATION] | |
averageTravelTime = abs(totalTime / len(journeys)) | |
return (len(journeys), int(averageTravelTime / CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR), | |
int(averageTravelTime % CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR)) | |
def displayTheTravelOptionsToTheUser(data): | |
computedAverageTimeTuple = getAverageTravelTime(data) | |
computedAverageArrivalTime = computeEstimatedArrivalTimeBasedOnAverageTime(computedAverageTimeTuple) | |
print( | |
"There are {} routes and {} TFL lines available to you when embarking on this journey and Average Travel Time across all {} routes is: {} hours and {} minutes." | |
"\nThe time now is {}:{} and your estimated arrival time is {}:{}". | |
format(len(data["journeys"]), len(data["lines"]), computedAverageTimeTuple[0], | |
computedAverageTimeTuple[1], computedAverageTimeTuple[2], computedAverageArrivalTime[0], | |
computedAverageArrivalTime[1], computedAverageArrivalTime[2], computedAverageArrivalTime[3])) | |
def retrieveJourneyWithShortestTravelDuration(data): | |
journeys = data[CONSTANT_JOURNEYS] | |
listOfDurations = {} | |
for journeyIndex in range(len(journeys)): | |
listOfDurations[journeyIndex] = [journeys[journeyIndex][CONSTANT_DURATION]] | |
return journeys[sorted(listOfDurations)[0]] | |
# to display a journey I need the index of the journey or just the journey data | |
# to display a journey I need the index of the journey or just the journey data | |
def displaySpecificJourneyDetails(data, journeyIndex=None): | |
""" if we pass index then get the journey by that index else use data as it is""" | |
singleJourneyWeAreInterestedIn = data | |
if journeyIndex != None: | |
journeys = data[CONSTANT_JOURNEYS] | |
singleJourneyWeAreInterestedIn = journeys[journeyIndex] | |
legsOfThisJourney = singleJourneyWeAreInterestedIn[CONSTANT_LEGS] | |
else: | |
legsOfThisJourney = data[CONSTANT_LEGS] | |
if singleJourneyWeAreInterestedIn.get(CONSTANT_FARE): | |
journeyFareInformation = "Travel Cost: £{}".format('{:,.2f}').format( | |
singleJourneyWeAreInterestedIn[CONSTANT_FARE][CONSTANT_TOTAL_COST] / 100) | |
else: | |
journeyFareInformation = "No Cost Information" | |
print("\n++++++++++++++++++++++++JOURNEY START++++++++++++++++++++++++++++++++++++++\n") | |
print("\nJourney Duration(Approx): {} --> {} \n".format(singleJourneyWeAreInterestedIn[CONSTANT_DURATION], | |
journeyFareInformation)) | |
displayTheDetailedStepsForThisJourney(legsOfThisJourney) | |
print("\n++++++++++++++++++++++++JOURNEY END++++++++++++++++++++++++++++++++++++++\n") | |
def displayTheDetailedStepsForThisJourney(data): | |
for indexOfThisJourney in range(len(data)): | |
singleJourneyLeg = data[indexOfThisJourney] | |
informationToDisplay = "Duration: {} mins {} {} {} {}{}" \ | |
.format(int(singleJourneyLeg[CONSTANT_DURATION]), "**** ", | |
singleJourneyLeg[CONSTANT_INSTRUCTION][ | |
CONSTANT_DETAILED], "**** ", | |
(lambda string: string.upper())(singleJourneyLeg[CONSTANT_MODE][ | |
CONSTANT_NAME]) | |
, " **** ") | |
print("\n{}".format(informationToDisplay)) | |
def displayEverthingJor(data): | |
print("\n\n") | |
journeys = data[CONSTANT_JOURNEYS] | |
for index in range(len(journeys)): | |
print(displaySpecificJourneyDetails(data, index)) | |
def computeEstimatedArrivalTimeBasedOnAverageTime(computedAverageTimeTuple): | |
localtime = time.localtime(time.time()) | |
hours = localtime.tm_hour | |
mins = localtime.tm_min | |
if (hours < 10): | |
formattedHours = "0{}".format(hours) | |
else: | |
formattedHours = "{}".format(hours) | |
if (mins < 10): | |
formattedMins = "0{}".format(mins) | |
else: | |
formattedMins = "{}".format(mins) | |
expectedArrivalTimeInHours = int(formattedHours) + computedAverageTimeTuple[1] | |
expectedArrivalTimeInMins = int(formattedMins) + computedAverageTimeTuple[2] | |
if (expectedArrivalTimeInMins > CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR): | |
expectedArrivalTimeInHours += expectedArrivalTimeInMins / CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR | |
expectedArrivalTimeInMins = expectedArrivalTimeInMins % CONSTANT_NUMBER_OF_MINUTES_IN_AN_HR | |
return (formattedHours, formattedMins, int(expectedArrivalTimeInHours), int(expectedArrivalTimeInMins)) | |
def submitRequestToTFL(completeUrlForTFL): | |
try: | |
r = requests.get(completeUrlForTFL, 10) | |
data = json.loads(r.content.decode()) | |
return data | |
except requests.exceptions.Timeout as requestTimedOutOrTookTooLong: | |
print(requestTimedOutOrTookTooLong) | |
sys.exit(1) | |
# Maybe set up for a retry, or continue in a retry loop | |
except requests.exceptions.TooManyRedirects as tooManyReDirects: | |
print(tooManyReDirects) | |
sys.exit(1) | |
# Omo! URL was bad try a different one | |
except requests.exceptions.RequestException as thatUrlYouSentWasDodgy: | |
print(thatUrlYouSentWasDodgy) | |
sys.exit(1) | |
if __name__ == '__main__': | |
while True: | |
user_input = [] | |
user_input = getTravelInstructionsFromUser() | |
isUserEnteredDataInTheRightFormat = validateUserEnteredInstructions(user_input) | |
if isUserEnteredDataInTheRightFormat != None: | |
print("Ok! Retrieving a route for you..Please wait a few seconds!") | |
completeUrlForTFL = completeTheUrlForTheRequestToTheServer(user_input) | |
print(completeUrlForTFL) | |
data = submitRequestToTFL(completeUrlForTFL) | |
if (data != None and data.get(CONSTANT_JOURNEYS) != None): | |
displayTheTravelOptionsToTheUser(data) | |
# display quickest journey yes or no | |
# input == yes # print(displaySpecificJourneyDetails(retrieveJourneyWithShortestTravelDuration(data))) | |
# input == no displayEverythingJor | |
howToDisplayTheJourneyInformationRetrieved = input( | |
"\nDo you wish to see the quickest journey to your destiznation? 'Y' or 'N': ").lower() | |
if howToDisplayTheJourneyInformationRetrieved == "y": | |
displaySpecificJourneyDetails(retrieveJourneyWithShortestTravelDuration(data)) | |
else: | |
displayEverthingJor(data) | |
else: | |
print("Sorry Couldn't find a viable route for you") | |
else: | |
print("Sorry there was a problem with the postcodes you entered!...Try again") | |
continueResponse = input("Do you wish to try finding a new journey? 'Y' or 'N' ").lower() | |
if (continueResponse == "y"): | |
continue | |
else: | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment