In [1]:
import requests
from pandas import Series, DataFrame
import pandas as pd
from collections import namedtuple
import folium
from math import radians, sin, cos, sqrt, atan2
In [2]:
# Using https://railwayapi.com/
base_url = "http://api.railwayapi.com/v2"
api_key = "apikey/your_api_key/"
In [3]:
def error_code_msg(error_code):
    if error_code == 210:
        return "{Train doesn't run on the date queried.}"
    elif error_code == 211:
        return "{Train doesn’t have journey class queried.}"
    elif error_code == 220:
        return "{Flushed PNR.}"
    elif error_code == 221:
        return "{Invalid PNR.}"
    elif error_code == 304:
        return "{Data couldn’t be fetched. No Data available.}"
    elif error_code == 404:
        return "{Data couldn’t be fetched. Request couldn’t go through.}"
    elif error_code == 504:
        return "{Argument error.}"
    elif error_code == 704:
        return "{Unauthorized user query. User account expired/exhausted or unregistered.}"
In [4]:
def code_to_df(station_code):
    api_method = "code-to-name"
    url = "/".join([base_url, api_method, "code", station_code, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        return DataFrame(resp_dict["stations"])
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [5]:
def code_info(station_code):
    df = code_to_df(station_code)
    if df is None:
        return
    elif df.empty:
        print("Apologies, no info found on that station code.")
    else:
        Station = namedtuple('Station', 'code name lat lng')
        result = df[df["code"] == station_code].iloc[0]
        return Station(code = station_code, name = result["name"], lat = result["lat"], lng = result["lng"])
In [6]:
def nearby_stations(station_code):
    df = code_to_df(station_code)
    if df is None:
        return
    elif df.empty:
        print("Apologies, no info found on that station code.")
    else:
        return df[df["code"] != station_code][["code", "name", "lat", "lng"]]
In [7]:
def find_station_by_name(partial_name):
    api_method = "suggest-station"
    url = "/".join([base_url, api_method, "name", partial_name, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        return DataFrame(resp_dict["stations"])[["code", "name"]]
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [8]:
def find_station_on_map(station_code, map_style = "pioneer", zoom_start_pioneer = 10, zoom_start_nat_geo = 6):
    station = code_info(station_code)
    if map_style == "nat_geo":
        m = folium.Map([station.lat, station.lng], zoom_start = zoom_start_nat_geo,
                       tiles = 'http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
                       attr = 'Tiles © Esri — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC')
    elif map_style == "pioneer":
        m = folium.Map([station.lat, station.lng], zoom_start = zoom_start_pioneer,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    else:
        print("map_style --", map_style, "not found, switching to default style.")
        m = folium.Map([station.lat, station.lng], zoom_start = zoom_start_pioneer,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09', 
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    folium.Marker([station.lat, station.lng], popup = station.name).add_to(m)
    return m
In [9]:
def find_nearby_stations_on_map(station_code, map_style = "pioneer"):
    df = code_to_df(station_code)
    station = df[df["code"] == station_code].loc[0]
    nearby = df[df["code"] != station_code]
    if map_style == "nat_geo":
        m = folium.Map([station.lat, station.lng], zoom_start = 6,
                       tiles = 'http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
                       attr = 'Tiles &copy; Esri &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC')
    elif map_style == "pioneer":
        m = folium.Map([station.lat, station.lng], zoom_start = 10,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    else:
        print("map_style --", map_style, "not found, switching to default style.")
        m = folium.Map([station.lat, station.lng], zoom_start = 10,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09', 
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    folium.Marker([station["lat"], station["lng"]], 
                      popup = station["name"], 
                      icon = folium.Icon(color='red', icon='map-marker')).add_to(m)
    for s in nearby.itertuples():
        folium.Marker([s.lat, s.lng], popup = s.name).add_to(m)
    return m        
In [10]:
def find_train_by_name(partial_train_name):
    api_method = "suggest-train"
    url = "/".join([base_url, api_method, "train", partial_train_name, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        return DataFrame(resp_dict["trains"])
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [11]:
def get_train_info(num_or_name):
    num_or_name = str(num_or_name)
    api_method = "name-number"
    url = "/".join([base_url, api_method, "train", num_or_name, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        Train = namedtuple('Train', 'number name runs_on')
        df = DataFrame(resp_dict["train"]["days"])
        runs_on = ",".join(df[df["runs"] == "Y"]["code"].tolist())
        return Train(name = resp_dict["train"]["name"], number = resp_dict["train"]["number"], runs_on = runs_on)
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [12]:
def get_train_route(train_num):
    train_num = str(train_num)
    api_method = "route"
    url = "/".join([base_url, api_method, "train", train_num, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        return DataFrame(resp_dict["route"])[["code", "fullname", "distance", "day", "halt", "scharr", "schdep"]]
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [13]:
def see_train_route(train_num, map_style = "pioneer"):
    df = get_train_route(train_num)
    df["location"] = df["code"].map(code_info)
    df["location"] = df["location"].map(lambda station: [station.lat, station.lng])
    locations = df["location"].tolist()
    source = df.iloc[0]
    source_popup = source["fullname"] + " (Source)" + ", Departure: " + source["schdep"] + ", Day: " + str(source["day"])
    dest = df.iloc[-1]
    dest_popup = dest["fullname"] + " (Destination)" + ", Arrival: " + dest["scharr"] + ", Day: " + str(dest["day"]) + ", Distance: " + str(dest["distance"]) + "km" 
    rest = df.iloc[1:-1]
    if map_style == "cartodb_dark":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5, tiles="Cartodb dark_matter")
    elif map_style == "pioneer":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    elif map_style == "nat_geo":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
                       attr = 'Tiles &copy; Esri &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC')
    else:
        print("map_style --", map_style, "not found, switching to default style.")
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09', 
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    folium.PolyLine(locations, color = "red", weight = 1.5).add_to(m)
    folium.Marker([source["location"][0], source["location"][1]], 
                  popup = source_popup, 
                  icon = folium.Icon(color='green', icon='ok-circle')).add_to(m)
    folium.Marker([dest["location"][0], dest["location"][1]], 
                      popup = dest_popup, 
                      icon = folium.Icon(color='red', icon='remove-circle')).add_to(m)
    for station in rest.itertuples():
        station_popup = station.fullname + ", Arrival: " + station.scharr + ", Departure: " + station.schdep + ", Halt: " + str(station.halt) + "min" + ", Day: " + str(station.day) + ", Distance: " + str(station.distance) + "km" 
        folium.Marker([station.location[0], station.location[1]], popup = station_popup).add_to(m)
    return m
In [14]:
def is_location_in_india(location):
    return (8.4 <= location[0] <= 37.6) and (68.7 <= location[1] <= 97.25)
In [15]:
def see_live_train_status(train_num, dd_mm_yyyy):
    api_method = "live"
    train_num = str(train_num)
    url = "/".join([base_url, api_method, "train", train_num, "date", dd_mm_yyyy, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        print("-----", "Live running status of:", resp_dict["train"]["number"], "(", resp_dict["train"]["name"], ")", "-----")
        pos = resp_dict["position"]
        latest_code = pos.partition("(")[2].partition(")")[0]
        print(pos)
        df = DataFrame(resp_dict["route"])
        df["name"] = df["station"].map(lambda station: station["name"])
        df["code"] = df["station"].map(lambda station: station["code"])
        df["location"] = df["code"].map(code_info)
        df["location"] = df["location"].map(lambda station: [station.lat, station.lng])
        del df["station_"]; del df["no"]; del df["station"]
        last_departed_df = df[(df["has_arrived"] == True) & (df["has_departed"] == True)]
        if last_departed_df.empty:
            pass
        else:
            last_departed_index = last_departed_df.index.tolist()[-1]
            df.loc[0:last_departed_index, "has_arrived"] = True
            df.loc[0:last_departed_index, "has_departed"] = True
        if pos.startswith("Train is currently at Source"):
            df.loc[:, "has_arrived"] = False
            df.loc[:, "has_departed"] = False
        if pos.startswith("Train has reached Destination"):
            df.loc[:, "has_arrived"] = True
            df.loc[:, "has_departed"] = True
        at_df = df[(df["has_arrived"] == True) & (df["has_departed"] == False)]
        if at_df.empty:
            pass
        else:
            at_index = at_df.index.tolist()[-1]
            df.loc[at_index, "has_arrived"] = True
            df.loc[at_index, "has_departed"] = True
            df.loc[at_index, "actdep"] = "on the station"
        reached = df[(df["has_arrived"] == True) & (df["has_departed"] == True)]
        unreached = df[(df["has_arrived"] == False) & (df["has_departed"] == False)]
        m = folium.Map([df.iloc[0]["location"][0], df.iloc[0]["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
        reached_locations = reached["location"].tolist()
        unreached_locations = unreached["location"].tolist()
        if latest_code in df["code"].values or latest_code == "":
            folium.PolyLine(reached_locations, color = "green", weight = 2).add_to(m)
            if reached_locations == []:
                folium.PolyLine(unreached_locations, color = "red", weight = 2).add_to(m)
            else:
                folium.PolyLine([reached_locations[-1]] + unreached_locations, color = "red", weight = 2).add_to(m)
        else:
            latest_code_info = code_info(latest_code)
            latest_code_loc = [latest_code_info.lat, latest_code_info.lng]
            if is_location_in_india(latest_code_loc):
                folium.PolyLine(reached_locations + [latest_code_loc], color = "green", weight = 2).add_to(m)
                folium.PolyLine([latest_code_loc] + unreached_locations, color = "red", weight = 2).add_to(m)
            else:
                folium.PolyLine(reached_locations, color = "green", weight = 2).add_to(m)
                folium.PolyLine([reached_locations[-1]] + unreached_locations, color = "red", weight = 2).add_to(m)
        for station in reached.itertuples():
            source_str = "Source:" if (station.scharr == "Source") else ""
            dest_str = "Destination:" if (station.schdep == "Destination") else ""
            reached_popup = source_str + dest_str + station.name + "(" + station.code + "), Status: " + station.status + ", Departure: " + station.actarr_date + "-- " + station.actdep  
            folium.Marker([station.location[0], station.location[1]],
                          popup = reached_popup,
                          icon = folium.Icon(color='green', icon='ok-circle')).add_to(m)
        for station in unreached.itertuples():
            source_str = "Source:" if (station.scharr == "Source") else ""
            dest_str = "Destination:" if (station.schdep == "Destination") else ""
            unreached_popup = source_str + dest_str + station.name + "(" + station.code + "), Scheduled arrival: " + station.scharr_date + "-- " + station.scharr
            folium.Marker([station.location[0], station.location[1]],
                          popup = unreached_popup,
                          icon = folium.Icon(color='red', icon='remove-circle')).add_to(m)
        return m
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [16]:
def live_train_status(train_num, dd_mm_yyyy):
    api_method = "live"
    train_num = str(train_num)
    url = "/".join([base_url, api_method, "train", train_num, "date", dd_mm_yyyy, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        print("-----", "Live running status of:", resp_dict["train"]["number"], "(", resp_dict["train"]["name"], ")", "-----")
        pos = resp_dict["position"]
        print(pos)
        df = DataFrame(resp_dict["route"])
        df["name"] = df["station"].map(lambda station: station["name"])
        df["code"] = df["station"].map(lambda station: station["code"])
        del df["station_"]; del df["no"]; del df["station"]
        return df
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return
In [17]:
def haversine_km(loc1, loc2):
    R = 6371
    lat1, lon1 = (loc1[0], loc1[1])
    lat2, lon2 = (loc2[0], loc2[1])
    
    phi_1 = radians(lat1)
    phi_2 = radians(lat2)
    
    delta_phi = radians(lat2-lat1)
    delta_lambda = radians(lon2-lon1)
    
    a = (sin(delta_phi/2) ** 2) + (cos(phi_1) * cos(phi_2) * sin(delta_lambda/2) ** 2)
    c = 2 * atan2(sqrt(a), sqrt(1-a))
    d = R * c
    return d
In [18]:
def see_corrected_train_route(train_num, map_style = "pioneer", override = ()):
    df = get_train_route(train_num)
    df["location"] = df["code"].map(code_info)
    df["location"] = df["location"].map(lambda station: [station.lat, station.lng])
    source_invalid = False
    dest_invalid = False
    codes = df["code"].tolist()
    locations = df["location"].tolist()
    origin_dist = df["distance"].tolist()
    actual_dist = [b - a for a, b in zip(origin_dist, origin_dist[1:])]
    calc_dist = [haversine_km(loc1, loc2) for loc1, loc2 in zip(locations, locations[1:])]
    error = [abs(act_d - calc_d) / act_d for act_d, calc_d in zip(actual_dist, calc_dist)]
    offending_indices = [index + 1 for ((index, err1), err2) in zip(enumerate(error), error[1:]) if (err1 >= 1) and (err2 >= 1)]
    if error[0] >= 1 and error[1] < 1:
        print("WARNING -- Not plotting the source station as the coordinates were invalid.")
        source_invalid = True
        offending_indices = offending_indices + [0]
    if error[-1] >= 1 and error[-2] < 1:
        print("WARNING -- Not plotting the destination station as the coordinates were invalid.")
        dest_invalid = True
        offending_indices = offending_indices + [df.shape[0] - 1]
    foreign_indices = [index for index, location in enumerate(locations) if not is_location_in_india(location)]
    overridden_indices = [index for index, code in enumerate(codes) if code in override]
    offending_indices = list(set(offending_indices + foreign_indices) - set(overridden_indices))
    df_offending = df.iloc[offending_indices]
    df = df.drop(offending_indices)
    locations = df["location"].tolist()
    source = df.iloc[0]
    if source_invalid:
        source_popup = source["fullname"] + ", Arrival: " + source["scharr"] + ", Departure: " + source["schdep"] + ", Halt: " + str(source["halt"]) + "min" + ", Day: " + str(source["day"]) + ", Distance: " + str(source["distance"]) + "km" 
    else:
        source_popup = source["fullname"] + " (Source)" + ", Departure: " + source["schdep"] + ", Day: " + str(source["day"])
    dest = df.iloc[-1]
    if dest_invalid:
        dest_popup = dest["fullname"] + ", Arrival: " + dest["scharr"] + ", Departure: " + dest["schdep"] + ", Halt: " + str(dest["halt"]) + "min" + ", Day: " + str(dest["day"]) + ", Distance: " + str(dest["distance"]) + "km"  
    else:
        dest_popup = dest["fullname"] + " (Destination)" + ", Arrival: " + dest["scharr"] + ", Day: " + str(dest["day"]) + ", Distance: " + str(dest["distance"]) + "km" 
    rest = df.iloc[1:-1]
    if map_style == "cartodb_dark":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5, tiles="Cartodb dark_matter")
    elif map_style == "pioneer":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    elif map_style == "nat_geo":
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
                       attr = 'Tiles &copy; Esri &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC')
    else:
        print("map_style --", map_style, "not found, switching to default style.")
        m = folium.Map([source["location"][0], source["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09', 
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
    folium.PolyLine(locations, color = "red", weight = 1.5).add_to(m)
    if source_invalid:
        folium.Marker([source["location"][0], source["location"][1]], 
                      popup = source_popup).add_to(m)
    else:
        folium.Marker([source["location"][0], source["location"][1]], 
                      popup = source_popup, 
                      icon = folium.Icon(color='green', icon='ok-circle')).add_to(m)
    if dest_invalid:
        folium.Marker([dest["location"][0], dest["location"][1]], 
                      popup = dest_popup).add_to(m)
    else:
        folium.Marker([dest["location"][0], dest["location"][1]], 
                      popup = dest_popup, 
                      icon = folium.Icon(color='red', icon='remove-circle')).add_to(m)
    for station in rest.itertuples():
        station_popup = station.fullname + ", Arrival: " + station.scharr + ", Departure: " + station.schdep + ", Halt: " + str(station.halt) + "min" + ", Day: " + str(station.day) + ", Distance: " + str(station.distance) + "km" 
        folium.Marker([station.location[0], station.location[1]], popup = station_popup).add_to(m)
    if not df_offending.empty:
        print("The coordinates for the following station(s) were invalid and hence they were not plotted:")
    for station in df_offending.itertuples():
        print("---", station.fullname, "(", station.code, ")", ", Arrival:", station.scharr, ", Departure:", station.schdep, ", Halt:", station.halt, "min", ", Day:", station.day, ", Distance:", station.distance, "km") 
    return m
In [19]:
def see_corrected_live_train_status(train_num, dd_mm_yyyy, override = ()):
    api_method = "live"
    train_num = str(train_num)
    url = "/".join([base_url, api_method, "train", train_num, "date", dd_mm_yyyy, api_key])
    resp = requests.get(url)
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError as e:
        print("Error:", str(e))
        return
    resp_dict = resp.json()
    if resp_dict["response_code"] == 200:
        print("-----", "Live running status of:", resp_dict["train"]["number"], "(", resp_dict["train"]["name"], ")", "-----")
        pos = resp_dict["position"]
        latest_code = pos.partition("(")[2].partition(")")[0]
        print(pos)
        df = DataFrame(resp_dict["route"])
        df["name"] = df["station"].map(lambda station: station["name"])
        df["code"] = df["station"].map(lambda station: station["code"])
        df["location"] = df["code"].map(code_info)
        df["location"] = df["location"].map(lambda station: [station.lat, station.lng])
        del df["station_"]; del df["no"]; del df["station"]
        last_departed_df = df[(df["has_arrived"] == True) & (df["has_departed"] == True)]
        if last_departed_df.empty:
            pass
        else:
            last_departed_index = last_departed_df.index.tolist()[-1]
            df.loc[0:last_departed_index, "has_arrived"] = True
            df.loc[0:last_departed_index, "has_departed"] = True
        if pos.startswith("Train is currently at Source"):
            df.loc[:, "has_arrived"] = False
            df.loc[:, "has_departed"] = False
        if pos.startswith("Train has reached Destination"):
            df.loc[:, "has_arrived"] = True
            df.loc[:, "has_departed"] = True
        at_df = df[(df["has_arrived"] == True) & (df["has_departed"] == False)]
        if at_df.empty:
            pass
        else:
            at_index = at_df.index.tolist()[-1]
            df.loc[at_index, "has_arrived"] = True
            df.loc[at_index, "has_departed"] = True
            df.loc[at_index, "actdep"] = "on the station"
        codes = df["code"].tolist()
        locations = df["location"].tolist()
        origin_dist = df["distance"].tolist()
        actual_dist = [b - a for a, b in zip(origin_dist, origin_dist[1:])]
        calc_dist = [haversine_km(loc1, loc2) for loc1, loc2 in zip(locations, locations[1:])]
        error = [abs(act_d - calc_d) / act_d for act_d, calc_d in zip(actual_dist, calc_dist)]
        offending_indices = [index + 1 for ((index, err1), err2) in zip(enumerate(error), error[1:]) if (err1 >= 1) and (err2 >= 1)]
        if error[0] >= 1 and error[1] < 1:
            print("WARNING -- Not plotting the source station as the coordinates were invalid.")
            offending_indices = offending_indices + [0]
        if error[-1] >= 1 and error[-2] < 1:
            print("WARNING -- Not plotting the destination station as the coordinates were invalid.")
            offending_indices = offending_indices + [df.shape[0] - 1]
        foreign_indices = [index for index, location in enumerate(locations) if not is_location_in_india(location)]
        overridden_indices = [index for index, code in enumerate(codes) if code in override]
        offending_indices = list(set(offending_indices + foreign_indices) - set(overridden_indices))
        df_offending = df.iloc[offending_indices]
        df = df.drop(offending_indices)
        reached = df[(df["has_arrived"] == True) & (df["has_departed"] == True)]
        unreached = df[(df["has_arrived"] == False) & (df["has_departed"] == False)]
        m = folium.Map([df.iloc[0]["location"][0], df.iloc[0]["location"][1]], zoom_start = 5,
                       tiles = 'https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=e6ccc046b8f44bbb996962128b904f09',
                       attr = '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>')
        reached_locations = reached["location"].tolist()
        unreached_locations = unreached["location"].tolist()
        if latest_code in df["code"].values or latest_code == "":
            folium.PolyLine(reached_locations, color = "green", weight = 2).add_to(m)
            if reached_locations == []:
                folium.PolyLine(unreached_locations, color = "red", weight = 2).add_to(m)
            else:
                folium.PolyLine([reached_locations[-1]] + unreached_locations, color = "red", weight = 2).add_to(m)
        else:
            latest_code_info = code_info(latest_code)
            latest_code_loc = [latest_code_info.lat, latest_code_info.lng]
            if is_location_in_india(latest_code_loc):
                folium.PolyLine(reached_locations + [latest_code_loc], color = "green", weight = 2).add_to(m)
                folium.PolyLine([latest_code_loc] + unreached_locations, color = "red", weight = 2).add_to(m)
            else:
                folium.PolyLine(reached_locations, color = "green", weight = 2).add_to(m)
                folium.PolyLine([reached_locations[-1]] + unreached_locations, color = "red", weight = 2).add_to(m)
        for station in reached.itertuples():
            source_str = "Source:" if (station.scharr == "Source") else ""
            dest_str = "Destination:" if (station.schdep == "Destination") else ""
            reached_popup = source_str + dest_str + station.name + "(" + station.code + "), Status: " + station.status + ", Departure: " + station.actarr_date + "-- " + station.actdep  
            folium.Marker([station.location[0], station.location[1]],
                          popup = reached_popup,
                          icon = folium.Icon(color='green', icon='ok-circle')).add_to(m)
        for station in unreached.itertuples():
            source_str = "Source:" if (station.scharr == "Source") else ""
            dest_str = "Destination:" if (station.schdep == "Destination") else ""
            unreached_popup = source_str + dest_str + station.name + "(" + station.code + "), Scheduled arrival: " + station.scharr_date + "-- " + station.scharr
            folium.Marker([station.location[0], station.location[1]],
                          popup = unreached_popup,
                          icon = folium.Icon(color='red', icon='remove-circle')).add_to(m)
        if not df_offending.empty:
            print("The coordinates for the following station(s) were invalid and hence they were not plotted:")
            print(df_offending[['name', 'code', 'status', 
                               'actarr', 'actarr_date', 'actdep', 'day',
                               'has_arrived', 'has_departed', 'scharr',
                               'scharr_date', 'schdep']].to_string())
        return m
    else:
        print("Apologies,", error_code_msg(resp_dict["response_code"]), "for the url --", url)
        return