Exemplo n.º 1
0
def test_create_beeline_footpaths():
    wattwil = Stop("Wattwil", "", "", 9.0864591001338, 47.2994765484827)
    wattwil_bahnhof = Stop("Wattwil, Bahnhof", "", "", 9.08679147678897, 47.2994582722627)
    pontresina = Stop("Pontresina", "", "", 9.89607473321206, 46.4906506582138)
    pontresina_bahnhof = Stop("Pontresina, Bahnhof", "", "", 9.89608371636491, 46.4910341056312)
    pontresina_post = Stop("Pontresina, Post", "", "", 9.90654010627351, 46.4880901497136)
    bern = Stop("Bern", "", "", 7.43911954873327, 46.9488249647708)
    bern_bahnhof = Stop("Bern, Bahnhof", "", "", 7.44020651022721, 46.948107473715)
    stops = [
        wattwil,
        wattwil_bahnhof,
        pontresina,
        pontresina_bahnhof,
        pontresina_post,
        bern,
        bern_bahnhof
    ]
    stops_per_id = {s.id: s for s in stops}
    footpaths = {
        Footpath(wattwil.id, wattwil_bahnhof.id, 120),
        Footpath(wattwil_bahnhof.id, wattwil.id, 120),
    }
    footpaths_per_from_to_stop_id = {(f.from_stop_id, f.to_stop_id): f for f in footpaths}
    create_beeline_footpaths(stops_per_id, footpaths_per_from_to_stop_id, beeline_distance=100, walking_speed=2 / 3.6)
    assert 120 == footpaths_per_from_to_stop_id[wattwil.id, wattwil_bahnhof.id].walking_time
    assert 120 == footpaths_per_from_to_stop_id[wattwil_bahnhof.id, wattwil.id].walking_time
    assert (pontresina.id, pontresina_bahnhof.id) in footpaths_per_from_to_stop_id
    assert (pontresina_bahnhof.id, pontresina.id) in footpaths_per_from_to_stop_id
    assert (pontresina.id, pontresina_post.id) not in footpaths_per_from_to_stop_id
    assert (pontresina_post.id, pontresina.id) not in footpaths_per_from_to_stop_id
    assert (bern.id, bern_bahnhof.id) not in footpaths_per_from_to_stop_id
    assert (bern_bahnhof.id, bern.id) not in footpaths_per_from_to_stop_id
Exemplo n.º 2
0
def test_make_transitive_nothing_to_do():
    footpaths = [
        Footpath("s1", "s2", 60),
        Footpath("s2", "s3", 70),
        Footpath("s1", "s3", 90),
    ]

    footpaths_per_from_to_stop_id = {(f.from_stop_id, f.to_stop_id): f for f in footpaths}

    make_transitive(footpaths_per_from_to_stop_id)

    assert 3 == len(footpaths_per_from_to_stop_id)
Exemplo n.º 3
0
def test_make_tranksitive_change_time():
    footpaths = [
        Footpath("s1", "s2", 60),
        Footpath("s2", "s3", 70),
        Footpath("s1", "s3", 140),
    ]

    footpaths_per_from_to_stop_id = {(f.from_stop_id, f.to_stop_id): f for f in footpaths}

    make_transitive(footpaths_per_from_to_stop_id)

    assert 3 == len(footpaths_per_from_to_stop_id)
    assert 130 == footpaths_per_from_to_stop_id[("s1", "s3")].walking_time
def check_for_transitivity(footpaths_per_from_to_stop_id):
    """Checks the footpaths for transitivity
    and returns missing footpaths and modified footpaths violating the triangle inequality.

    More precisely:
    - if there are three stops s_1, s_2 and s_3 with a footpath from s_1 to s_2 and a footpath from s_2 to s_3,
    but no footpath from s_1 to s_3, a new footpath from s_1 to s_3 is created (with walking time equal to the sum
    of the walking time of the two existing footpaths.
    This new footpath is added to the list returned in the first entry of the returned tuple.
    - if there are three stops s_1, s_2 and s_3 with a footpath from s_1 to s_2, s_2 to s_3 and s_1 to s_3, but the
    sum of the walking times of s_1 to s_2 and s_2 to s_3 is smaller than the walking time from s_1 to s_3,
    a new footpath from s_1 to s_3 is created with walking time equal to the sum of the two other walking times
    (only if this sum is positive).
    This new footpath is added to the list returned in the second entry of the returned tuple.

    Args:
        footpaths_per_from_to_stop_id (dict): footpath per (from_stop_id, to_stop_id)-tuple.

    Returns:
        tuple: new and modified footpaths (see above for the details).
    """
    log_start("checking footpaths for transitivity", log)
    footpaths_per_from_stop_id = {}
    for (from_stop_id, _), footpath in footpaths_per_from_to_stop_id.items():
        footpaths_per_from_stop_id[
            from_stop_id] = footpaths_per_from_stop_id.get(from_stop_id,
                                                           []) + [footpath]
    new_footpaths = []
    footpaths_with_time_change = []
    footpaths_per_from_stop_id = dict(footpaths_per_from_stop_id)
    for from_stop_id, outgoing_footpaths in footpaths_per_from_stop_id.items():
        for first_footpath in outgoing_footpaths:
            for second_footpath in footpaths_per_from_stop_id.get(
                    first_footpath.to_stop_id, []):
                second_to_stop_id = second_footpath.to_stop_id
                new_wt = first_footpath.walking_time + second_footpath.walking_time
                if (from_stop_id, second_to_stop_id
                    ) not in footpaths_per_from_to_stop_id:
                    new_footpaths += [
                        Footpath(from_stop_id, second_to_stop_id, new_wt)
                    ]
                elif 0 < new_wt < footpaths_per_from_to_stop_id[(
                        from_stop_id, second_to_stop_id)].walking_time:
                    footpaths_with_time_change += [
                        Footpath(from_stop_id, second_to_stop_id, new_wt)
                    ]
    log_end()
    return new_footpaths, footpaths_with_time_change
Exemplo n.º 5
0
def test_make_transitive_simple():
    footpaths = [
        Footpath("s1", "s2", 60),
        Footpath("s2", "s3", 70),
    ]

    footpaths_per_from_to_stop_id = {(f.from_stop_id, f.to_stop_id): f for f in footpaths}

    make_transitive(footpaths_per_from_to_stop_id)

    assert 3 == len(footpaths_per_from_to_stop_id)

    footpath_s1_s3 = footpaths_per_from_to_stop_id[("s1", "s3")]
    assert "s1" == footpath_s1_s3.from_stop_id
    assert "s3" == footpath_s1_s3.to_stop_id
    assert 130 == footpath_s1_s3.walking_time
Exemplo n.º 6
0
def test_make_transitive_two_iterations():
    footpaths = [
        Footpath("s1", "s2", 60),
        Footpath("s2", "s3", 70),
        Footpath("s3", "s4", 70),
    ]

    footpaths_per_from_to_stop_id = {(f.from_stop_id, f.to_stop_id): f for f in footpaths}

    make_transitive(footpaths_per_from_to_stop_id)

    assert 6 == len(footpaths_per_from_to_stop_id)

    assert 130 == footpaths_per_from_to_stop_id[("s1", "s3")].walking_time
    assert 200 == footpaths_per_from_to_stop_id[("s1", "s4")].walking_time
    assert 140 == footpaths_per_from_to_stop_id[("s2", "s4")].walking_time
def create_beeline_footpaths(stops_per_id, footpaths_per_from_to_stop_id, beeline_distance, walking_speed):
    """Creates for every stop new footpaths to the other stops within the beeline distance
    if they are not already defined.

    The new footpaths are added to footpaths_per_from_to_stop_id.
    The walking time of the new footpath is calculated from the beeline distance and the specific walking speed.

    Args:
        stops_per_id (dict): stops per stop id.
        footpaths_per_from_to_stop_id (): footpaths per (from_stop_id, to_stop_id) tuple.
        beeline_distance (float): the beeline distance in meters.
        walking_speed (float): walking speed in meters per second.
    """
    nb_footpaths_perimeter = 0
    log_start("adding footpaths in beeline perimeter with radius {}m".format(beeline_distance), log)
    log_start("transforming coordinates", log)
    stop_list = list(stops_per_id.values())
    easting_northing_list = [(s.easting, s.northing) for s in stop_list]
    x_y_coordinates = [wgs84_to_spherical_mercator(p[0], p[1]) for p in easting_northing_list]
    log_end()
    log_start("creating quadtree for fast perimeter search", log)
    tree = spatial.KDTree(x_y_coordinates)
    log_end()
    log_start("perimeter search around every stop", log)
    for ind, a_stop in enumerate(stop_list):
        x_y_a_stop = x_y_coordinates[ind]
        for another_ind in tree.query_ball_point(x_y_a_stop, beeline_distance):
            x_y_another_stop = x_y_coordinates[another_ind]
            d = distance(x_y_a_stop, x_y_another_stop)
            # distance in meters.
            # note that distances can be very inaccurate (up to factor 2 on a lat of 60°), but this should be ok here.
            walking_time = d / walking_speed
            another_stop = stop_list[another_ind]
            key = (a_stop.id, another_stop.id)
            if key not in footpaths_per_from_to_stop_id:
                footpaths_per_from_to_stop_id[key] = Footpath(key[0], key[1], walking_time)
                nb_footpaths_perimeter += 1
            if (key[1], key[0]) not in footpaths_per_from_to_stop_id:
                footpaths_per_from_to_stop_id[(key[1], key[0])] = Footpath(key[1], key[0], walking_time)
                nb_footpaths_perimeter += 1
    log_end()
    log_end(additional_message="# footpath within perimeter added: {}. # footpaths total: {}".format(
        nb_footpaths_perimeter,
        len(footpaths_per_from_to_stop_id)
    ))
def test_journey_leg_1():
    journey = Journey()
    journey.prepend_journey_leg(
        JourneyLeg(Connection("t2", "s7", "s8", 50, 60),
                   Connection("t2", "s12", "s13", 80, 90), None))
    journey.prepend_journey_leg(
        JourneyLeg(Connection("t1", "s1", "s2", 10, 20),
                   Connection("t1", "s5", "s6", 30, 40),
                   Footpath("s6", "s7", 1)))
    journey.prepend_journey_leg(JourneyLeg(None, None, Footpath("s0", "s1",
                                                                2)))
    assert journey.has_legs()
    assert journey.is_first_leg_footpath()
    assert not journey.is_last_leg_footpath()
    assert "s0" == journey.get_first_stop_id()
    assert "s13" == journey.get_last_stop_id()
    assert 3 == journey.get_nb_journey_legs()
    assert 2 == journey.get_nb_pt_journey_legs()
def test_journey_leg_without_in_out_connection():
    footpath = Footpath("s6", "s7", 1)
    journey_leg = JourneyLeg(None, None, footpath)
    assert journey_leg.in_connection is None
    assert journey_leg.out_connection is None
    assert footpath == journey_leg.footpath
    assert journey_leg.get_in_stop_id() is None
    assert journey_leg.get_out_stop_id() is None
    assert journey_leg.get_dep_time_in_stop_id() is None
    assert journey_leg.get_arr_time_out_stop_id() is None
def test_journey_prepend_journey_leg_not_stop_consistent_1():
    journey = Journey()
    journey.prepend_journey_leg(
        JourneyLeg(Connection("t2", "s6", "s8", 50, 60),
                   Connection("t2", "s12", "s13", 80, 90), None))
    with pytest.raises(ValueError):
        journey.prepend_journey_leg(
            JourneyLeg(Connection("t1", "s1", "s2", 10, 20),
                       Connection("t1", "s5", "s6", 30, 40),
                       Footpath("s6", "s7", 1)))
def test_connectionscan_data_constructor_basic():
    stops_per_id = {
        "1": Stop("1", "c1", "n1", 0.0, 0.0),
        "2": Stop("2", "c2", "n2", 1.0, 1.0),
        "2a": Stop("2a", "c2a", "n2a", 1.1, 1.1),
        "3": Stop("3", "c3", "n3", 3.0, 3.0),
    }

    footpaths_per_from_to_stop_id = {
        ("1", "1"): Footpath("1", "1", 60),
        ("2", "2"): Footpath("2", "2", 70),
        ("2a", "2a"): Footpath("2a", "2a", 71),
        ("3", "3"): Footpath("3", "3", 80),
        ("2", "2a"): Footpath("2", "2a", 75),
        ("2a", "2"): Footpath("2a", "2", 75),
    }

    con_1_1 = Connection("t1", "1", "2", 60, 70)
    con_1_2 = Connection("t1", "2", "3", 72, 80)

    con_2_1 = Connection("t2", "2", "3", 50, 59)
    con_2_2 = Connection("t2", "3", "1", 60, 72)

    trips_per_id = {
        "t1": Trip("t1", [con_1_1, con_1_2]),
        "t2": Trip("t2", [con_2_1, con_2_2])
    }
    cs_data = ConnectionScanData(stops_per_id, footpaths_per_from_to_stop_id, trips_per_id)
    assert 4 == len(cs_data.stops_per_id)
    assert 4 == len(cs_data.stops_per_id)
    assert 2 == len(cs_data.trips_per_id)
    assert [con_2_1, con_1_1, con_2_2, con_1_2] == cs_data.sorted_connections
def test_journey_leg():
    in_connection = Connection("t1", "s1", "s2", 10, 20)
    out_connection = Connection("t1", "s5", "s6", 30, 40)
    footpath = Footpath("s6", "s7", 1)
    journey_leg = JourneyLeg(in_connection, out_connection, footpath)
    assert in_connection == journey_leg.in_connection
    assert out_connection == journey_leg.out_connection
    assert footpath == journey_leg.footpath
    assert "t1" == journey_leg.get_trip_id()
    assert "s1" == journey_leg.get_in_stop_id()
    assert "s6" == journey_leg.get_out_stop_id()
    assert "s1" == journey_leg.get_first_stop_id()
    assert "s7" == journey_leg.get_last_stop_id()
    assert 10 == journey_leg.get_dep_time_in_stop_id()
    assert 40 == journey_leg.get_arr_time_out_stop_id()
def create_test_connectionscan_data():
    stops_per_id = {
        s.id: s
        for s in [
            fribourg,
            bern,
            zuerich_hb,
            winterthur,
            st_gallen,
            interlaken_ost,
            basel_sbb,
            chur,
            thusis,
            samedan,
            st_moritz,
            bern_duebystrasse,
            koeniz_zentrum,
            bern_bahnhof,
            ostermundigen_bahnhof,
            samedan_bahnhof,
            samedan_spital,
        ]
    }

    footpaths_per_from_stop_to_stop_id = {(s.id, s.id):
                                          Footpath(s.id, s.id, 2 * 60)
                                          for s in stops_per_id.values()}
    footpaths_per_from_stop_to_stop_id[(zuerich_hb.id,
                                        zuerich_hb.id)] = Footpath(
                                            zuerich_hb.id, zuerich_hb.id,
                                            7 * 60)
    footpaths_per_from_stop_to_stop_id[(bern.id, bern.id)] = Footpath(
        bern.id, bern.id, 5 * 60)
    footpaths_per_from_stop_to_stop_id[(bern_bahnhof.id, bern.id)] = Footpath(
        bern_bahnhof.id, bern.id, 5 * 60)
    footpaths_per_from_stop_to_stop_id[(bern.id, bern_bahnhof.id)] = Footpath(
        bern.id, bern_bahnhof.id, 5 * 60)
    footpaths_per_from_stop_to_stop_id[(chur.id, chur.id)] = Footpath(
        chur.id, chur.id, 4 * 60)
    footpaths_per_from_stop_to_stop_id[(samedan.id,
                                        samedan_bahnhof.id)] = Footpath(
                                            samedan.id, samedan_bahnhof.id,
                                            3 * 60)
    footpaths_per_from_stop_to_stop_id[(samedan_bahnhof.id,
                                        samedan.id)] = Footpath(
                                            samedan_bahnhof.id, samedan.id,
                                            3 * 60)

    trips = []

    trips += get_forth_and_back_trips(
        [fribourg, bern, zuerich_hb, winterthur, st_gallen],
        [22 * 60, 56 * 60, 26 * 60, 35 * 60], [6 * 60, 9 * 60, 3 * 60],
        hhmmss_to_sec("05:34:00"), 32, 30 * 60)

    trips += get_forth_and_back_trips([interlaken_ost, bern, basel_sbb],
                                      [52 * 60, 55 * 60], [12 * 60],
                                      hhmmss_to_sec("05:00:00"), 16, 60 * 60)

    trips += get_forth_and_back_trips([basel_sbb, zuerich_hb, chur],
                                      [53 * 60, 75 * 60], [11 * 60],
                                      hhmmss_to_sec("05:33:00"), 16, 60 * 60)

    trips += get_forth_and_back_trips([chur, thusis, samedan, st_moritz],
                                      [30 * 60, 75 * 60, 12 * 60],
                                      [2 * 60, 6 * 60],
                                      hhmmss_to_sec("05:58:00"), 16, 60 * 60)

    trips += get_forth_and_back_trips([
        koeniz_zentrum, bern_duebystrasse, bern_bahnhof, ostermundigen_bahnhof
    ], [6 * 60, 7 * 60, 15 * 60], [0, 0], hhmmss_to_sec("05:00:00"), 10 * 16,
                                      6 * 60)

    trips += get_forth_and_back_trips([samedan_bahnhof, samedan_spital],
                                      [7 * 60], [], hhmmss_to_sec("15:00:00"),
                                      1, 24 * 60 * 60)
    return ConnectionScanData(stops_per_id, footpaths_per_from_stop_to_stop_id,
                              {t.id: t
                               for t in trips})
def test_journey_leg_constructor_not_time_consistent():
    in_connection = Connection("t1", "s1", "s2", 10, 20)
    out_connection = Connection("t1", "s5", "s6", 5, 9)
    footpath = Footpath("s6", "s7", 1)
    with pytest.raises(ValueError):
        JourneyLeg(in_connection, out_connection, footpath)
def test_footpath_constructor():
    a_footpath = Footpath("1", "2", 60)
    assert "1" == a_footpath.from_stop_id
    assert "2" == a_footpath.to_stop_id
    assert 60 == a_footpath.walking_time
def test_journey_leg_constructor_not_in_out_connection_consistent_2():
    in_connection = None
    out_connection = Connection("t1", "s5", "s6", 30, 40)
    footpath = Footpath("s10", "s7", 1)
    with pytest.raises(ValueError):
        JourneyLeg(in_connection, out_connection, footpath)
Exemplo n.º 17
0
def parse_gtfs(
        path_to_gtfs_zip,
        desired_date,
        add_beeline_footpaths=True,
        beeline_distance=100.0,
        walking_speed=2.0 / 3.6,
        make_footpaths_transitive=False
):
    """Parses a gtfs-file and returns the corresponding timetable data of a specific date.

    In many GTFS files the information about the footpaths/transfers is not complete.
    In these cases it is recommended to define appropriate footpaths within a beeline distance.

    Args:
        path_to_gtfs_zip (str): path to the gtfs-file (weblink or path to a zip-file).
        desired_date (date): date on which the timetable data is read.
        add_beeline_footpaths (obj:`bool`, optional): specifies whether footpaths should be created
        depending on the beeline (air distance) and independent of the transfers.txt gtfs-file or not.
        beeline_distance (obj:`float`, optional): radius in meter of the perimeter (circle) to create
        the beeline footpaths (only relevant if add_beeline_footpaths is True).
        walking_speed (obj:`float`, optional): walking speed in meters per second for calculating the walking time
        of the created beeline footpaths (only relevant if add_beeline_footpaths is True).
        make_footpaths_transitive (obj:`bool`, optional): True if the footpaths are to be made transitive, else False.
        Making footpaths transitive can lead to long running times and implausible results.

    Returns:
        ConnectionScanData: timetable data of the specific date.
    """
    log_start("parsing gtfs-file for desired date {} ({})".format(desired_date, path_to_gtfs_zip), log)
    stops_per_id = {}
    footpaths_per_from_to_stop_id = {}
    trips_per_id = {}

    with ZipFile(path_to_gtfs_zip, "r") as zip_file:
        log_start("parsing stops.txt", log)
        with zip_file.open("stops.txt", "r") as gtfs_file:  # required
            reader = csv.reader(TextIOWrapper(gtfs_file, ENCODING))
            header = next(reader)
            id_index = header.index("stop_id")  # required
            code_index = get_index_with_default(header, "stop_code")  # optional
            name_index = get_index_with_default(header, "stop_name")  # conditionally required
            lat_index = get_index_with_default(header, "stop_lat")  # conditionally required
            lon_index = get_index_with_default(header, "stop_lon")  # conditionally required
            location_type_index = get_index_with_default(header, "location_type")
            parent_station_index = get_index_with_default(header, "parent_station")
            for row in reader:
                stop_id = row[id_index]
                is_station = row[location_type_index] == "1" if location_type_index else False
                parent_station_id = ((row[parent_station_index] if row[parent_station_index] != "" else None)
                                     if parent_station_index else None)
                stops_per_id[stop_id] = Stop(
                    stop_id,
                    row[code_index] if code_index else "",
                    row[name_index] if name_index else "",
                    float(row[lon_index]) if lon_index else 0.0,
                    float(row[lat_index]) if lat_index else 0.0,
                    is_station=is_station,
                    parent_station_id=parent_station_id
                )
        log_end(additional_message="# stops: {}".format(len(stops_per_id)))

        log_start("parsing transfers.txt", log)
        if "transfers.txt" in zip_file.namelist():
            with zip_file.open("transfers.txt", "r") as gtfs_file:  # optional
                reader = csv.reader(TextIOWrapper(gtfs_file, ENCODING))
                header = next(reader)
                from_stop_id_index = header.index("from_stop_id")  # required
                to_stop_id_index = header.index("to_stop_id")  # required
                transfer_type_index = header.index("transfer_type")  # required
                min_transfer_time_index = get_index_with_default(header, "min_transfer_time")  # optional
                if min_transfer_time_index:
                    nb_footpaths_not_added = 0
                    for row in reader:
                        if row[transfer_type_index] == "2":
                            from_stop_id = row[from_stop_id_index]
                            to_stop_id = row[to_stop_id_index]
                            if from_stop_id in stops_per_id and to_stop_id in stops_per_id:
                                footpaths_per_from_to_stop_id[(from_stop_id, to_stop_id)] = Footpath(
                                    from_stop_id,
                                    to_stop_id,
                                    int(row[min_transfer_time_index])
                                )
                            else:
                                nb_footpaths_not_added += 1
                                log.debug(("footpath from {} to {} cannot be defined since not both stops are defined "
                                           "in stops.txt").format(from_stop_id, to_stop_id))
                    if nb_footpaths_not_added > 0:
                        log.info(("{} rows from transfers.txt were not added to footpaths since either the "
                                  "from_stop_id or to_stop_id is not defined in stops.txt.").format(
                            nb_footpaths_not_added))
                else:
                    raise ValueError(("min_transfer_time column in gtfs transfers.txt file is not defined, "
                                      "cannot calculate footpaths."))
        log_end(additional_message="# footpaths from transfers.txt: {}".format(len(footpaths_per_from_to_stop_id)))
        log_start("adding footpaths to parent station", log)
        nb_parent_footpaths = 0
        for a_stop in stops_per_id.values():
            if a_stop.parent_station_id is not None:
                key = (a_stop.id, a_stop.parent_station_id)
                if key not in footpaths_per_from_to_stop_id:
                    footpaths_per_from_to_stop_id[key] = Footpath(key[0], key[1], 0)
                    nb_parent_footpaths += 1
                if (key[1], key[0]) not in footpaths_per_from_to_stop_id:
                    footpaths_per_from_to_stop_id[(key[1], key[0])] = Footpath(key[1], key[0], 0)
                    nb_parent_footpaths += 1
        log_end(additional_message="# footpath from/to parent_station added: {}. # footpaths total: {}".format(
            nb_parent_footpaths, len(footpaths_per_from_to_stop_id)))
        log_start("adding footpaths within stops (if not defined)", log)
        nb_loops = 0
        for stop_id in stops_per_id.keys():
            from_to_stop_id = (stop_id, stop_id)
            if from_to_stop_id not in footpaths_per_from_to_stop_id:
                footpaths_per_from_to_stop_id[from_to_stop_id] = Footpath(stop_id, stop_id, 0)  # best guess!!
                nb_loops += 1
        log_end(additional_message="# footpath loops added: {}, # footpaths total: {}".format(nb_loops, len(
            footpaths_per_from_to_stop_id)))

        if add_beeline_footpaths:
            create_beeline_footpaths(stops_per_id, footpaths_per_from_to_stop_id, beeline_distance, walking_speed)
        else:
            log.info("adding beeline footpaths is deactivated")

        if make_footpaths_transitive:
            make_transitive(footpaths_per_from_to_stop_id)
        else:
            log.info("making footpaths transitive is deactivated")

        log_start("parsing calendar.txt and calendar_dates.txt", log)
        service_available_at_date_per_service_id = get_service_available_at_date_per_service_id(zip_file, desired_date)
        log_end()

        log_start("parsing trips.txt", log)
        trip_available_at_date_per_trip_id, route_id_per_trip_id = \
            get_trip_available_at_date_per_trip_id(zip_file, service_available_at_date_per_service_id)
        if len(trip_available_at_date_per_trip_id):
            msg = "# trips available at {}: {}".format(desired_date, len(trip_available_at_date_per_trip_id))
        else:
            msg = "no trips available at {}. assure that the date is within the timetable period.".format(desired_date)
        log_end(additional_message=msg)

        log_start("parsing routes.txt and assigning route_type to trip_id", log)
        with zip_file.open("routes.txt", "r") as gtfs_file:  # required
            reader = csv.reader(TextIOWrapper(gtfs_file, ENCODING))
            header = next(reader)
            route_id_index = header.index("route_id")  # required
            route_type_index = header.index("route_type")  # required
            route_type_per_route_id = {}
            for row in reader:
                route_type_per_route_id[row[route_id_index]] = int(row[route_type_index])
        route_type_per_trip_id = {trip_id: route_type_per_route_id[route_id_per_trip_id[trip_id]]
                                  for trip_id in route_id_per_trip_id}
        log_end()

        log_start("parsing stop_times.txt", log)
        with zip_file.open("stop_times.txt", "r") as gtfs_file:  # required
            reader = csv.reader(TextIOWrapper(gtfs_file, ENCODING))
            header = next(reader)
            trip_id_index = header.index("trip_id")  # required
            stop_id_index = header.index("stop_id")  # required
            arrival_time_index = get_index_with_default(header, "arrival_time")  # conditionally required
            departure_time_index = get_index_with_default(header, "departure_time")  # conditionally required

            def process_rows_of_trip(rows):
                if rows:
                    trip_id = rows[0][trip_id_index]
                    if trip_available_at_date_per_trip_id[trip_id]:
                        connections = []
                        for i in range(len(rows) - 1):
                            from_row = rows[i]
                            to_row = rows[i + 1]
                            con_dep = from_row[departure_time_index] if departure_time_index else None
                            con_arr = to_row[arrival_time_index] if arrival_time_index else None
                            if con_dep and con_arr:
                                connections += [Connection(
                                    trip_id,
                                    from_row[stop_id_index],
                                    to_row[stop_id_index],
                                    hhmmss_to_sec(con_dep),
                                    hhmmss_to_sec(con_arr))]
                            else:
                                return  # we do not want trips with missing times

                        try:
                            trip_type = TripType(route_type_per_trip_id[trip_id])
                        except ValueError:
                            trip_type = TripType.UNKNOWN
                        trips_per_id[trip_id] = Trip(trip_id, connections, trip_type)

            last_trip_id = None
            row_list = []
            for row in reader:
                act_trip_id = row[trip_id_index]
                if last_trip_id == act_trip_id:
                    row_list += [row]
                else:
                    process_rows_of_trip(row_list)
                    last_trip_id = act_trip_id
                    row_list = [row]
            process_rows_of_trip(row_list)
        log_end(additional_message="# trips: {}".format(len(trips_per_id)))

    cs_data = ConnectionScanData(stops_per_id, footpaths_per_from_to_stop_id, trips_per_id)
    log_end(additional_message="{}".format(cs_data))
    return cs_data
def test_connectionscan_data_constructor_stops_in_footpath_and_stops_not_consistent():
    with pytest.raises(ValueError):
        ConnectionScanData({"s1": Stop("s1", "", "", 0.0, 0.0)}, {("s1", "s2"): Footpath("s1", "s2", 60)}, {})
    log_end(additional_message="test failed successful")