Exemplo n.º 1
0
def match_nextbus_direction(nextbus_route_config, geometry):

    shape_start = geometry.coords[0]
    shape_end = geometry.coords[-1]

    nextbus_dir_infos = nextbus_route_config.get_direction_infos()

    terminal_dists = []

    for nextbus_dir_info in nextbus_dir_infos:

        nextbus_dir_stop_ids = nextbus_dir_info.get_stop_ids()

        first_stop_info = nextbus_route_config.get_stop_info(nextbus_dir_stop_ids[0])
        last_stop_info = nextbus_route_config.get_stop_info(nextbus_dir_stop_ids[-1])

        # Determine distance between first nextbus stop and start of GTFS shape,
        # plus distance between last stop and end of GTFS shape,
        # for all Nextbus directions for this route.
        start_dist = util.haver_distance(first_stop_info.lat, first_stop_info.lon, shape_start[1], shape_start[0])
        end_dist = util.haver_distance(last_stop_info.lat, last_stop_info.lon, shape_end[1], shape_end[0])

        terminal_dist = start_dist + end_dist
        terminal_dists.append(terminal_dist)

    terminal_dist_order = np.argsort(terminal_dists)

    best_nextbus_dir_index = terminal_dist_order[0] # index of the "best" shape for this direction, with the minimum terminal_dist

    best_nextbus_dir_info = nextbus_dir_infos[best_nextbus_dir_index]
    best_terminal_dist = terminal_dists[best_nextbus_dir_index]

    return best_nextbus_dir_info, best_terminal_dist
def get_stop_geometry(route, stop_id, xy_geometry, shape_lat, shape_lon, shape_cumulative_dist):
    stop_info = route.get_stop_info(stop_id)

    stop_dist_to_shape_coords = util.haver_distance(shape_lat, shape_lon, stop_info.lat, stop_info.lon)

    closest_index = int(np.argmin(stop_dist_to_shape_coords))

    # determine the offset distance between the stop and the line after the closest coord,
    # and between the stop and the line after the closest coord.
    # Need to project lon/lat coords to x/y here in order for shapely to determine the distance between
    # a point and a line (shapely doesn't support distance for lon/lat coords)

    stop_xy = shapely.geometry.Point(project_xy(stop_info.lon, stop_info.lat))

    geom_length = len(xy_geometry.coords)

    if closest_index < geom_length - 1:
        next_line = shapely.geometry.LineString(xy_geometry.coords[closest_index:closest_index+2])
        next_line_offset = next_line.distance(stop_xy)

    if closest_index > 0:
        prev_line = shapely.geometry.LineString(xy_geometry.coords[closest_index-1:closest_index+1])
        prev_line_offset = prev_line.distance(stop_xy)

    if closest_index == 0:
        stop_after_index = 0
        offset = next_line_offset
    elif closest_index + 1 >= geom_length:
        stop_after_index = closest_index - 1
        offset = prev_line_offset
    else:
        offset = min(next_line_offset, prev_line_offset)
        stop_after_index = closest_index if next_line_offset < prev_line_offset else closest_index - 1

    stop_dist = shape_cumulative_dist[stop_after_index] + stop_dist_to_shape_coords[stop_after_index]

    return {
        'distance': int(stop_dist), # total distance in meters along the route shape to this stop
        'after_index': stop_after_index, # the index of the coordinate of the shape just before this stop
        'offset': int(offset) # distance in meters between this stop and the closest line segment of shape
    }
Exemplo n.º 3
0
        print(f"Route: {route_id} ({route_config.title})")

        dir_infos = route_config.get_direction_infos()

        for dir_info in dir_infos:
            print(f"Direction: {dir_info.title} ({dir_info.id})")

            prev_stop_info = None

            for dir_index, stop_id in enumerate(dir_info.get_stop_ids()):
                stop_info = route_config.get_stop_info(stop_id)

                if prev_stop_info is not None:
                    delta_dist = util.haver_distance(stop_info.lat,
                                                     stop_info.lon,
                                                     prev_stop_info.lat,
                                                     prev_stop_info.lon)
                else:
                    delta_dist = np.nan

                stop_arrivals = df[(df['SID'] == stop_info.id)
                                   & (df['DID'] == dir_info.id)]
                dwell_time = (stop_arrivals['DEPARTURE_TIME'] -
                              stop_arrivals['TIME'])

                if not stop_arrivals.empty:
                    min_dist_quantiles = np.quantile(stop_arrivals['DIST'],
                                                     [0, 0.5, 1])
                    dwell_time_quantiles = np.quantile(dwell_time, [0, 0.5, 1])
                else:
                    min_dist_quantiles = []
# In order to determine where a Nextbus stop appears along a GTFS shape, this finds the closest coordinate of the
# GTFS shape to the Nextbus stop, then determines whether the stop is closer to the line between that GTFS shape coordinate
# and the previous GTFS coordinate, or the line between that GTFS shape coordinate and the next GTFS coordinate.
# (This may not always be correct for shapes that loop back on themselves.)
#
# Currently the script just overwrites the one S3 path, but this process could be extended in the future to
# store different paths for different dates, to allow fetching historical data for route configurations.
#

agency_id = 'sf-muni'
gtfs_url = 'http://gtfs.sfmta.com/transitdata/google_transit.zip'
center_lat = 37.772
center_lon = -122.442
version = 'v2'

deg_lat_dist = util.haver_distance(center_lat, center_lon, center_lat-0.1, center_lon)*10
deg_lon_dist = util.haver_distance(center_lat, center_lon, center_lat, center_lon-0.1)*10

# projection function from lon/lat coordinates in degrees (z ignored) to x/y coordinates in meters.
# satisfying the interface of shapely.ops.transform (https://shapely.readthedocs.io/en/stable/manual.html#shapely.ops.transform).
# This makes it possible to use shapely methods to calculate the distance in meters between geometries
def project_xy(lon, lat, z=None):
    return (round((lon - center_lon) * deg_lon_dist, 1), round((lat - center_lat) * deg_lat_dist, 1))

def get_stop_geometry(route, stop_id, xy_geometry, shape_lat, shape_lon, shape_cumulative_dist):
    stop_info = route.get_stop_info(stop_id)

    stop_dist_to_shape_coords = util.haver_distance(shape_lat, shape_lon, stop_info.lat, stop_info.lon)

    closest_index = int(np.argmin(stop_dist_to_shape_coords))
Exemplo n.º 5
0
        def add_direction(id, gtfs_shape_id, gtfs_direction_id, stop_ids, title = None):

            if title is None:
                default_direction_info = agency.default_directions.get(gtfs_direction_id, {})
                title_prefix = default_direction_info.get('title_prefix', None)

                last_stop_id = stop_ids[-1]
                last_stop = stops_map[last_stop_id]

                if title_prefix is not None:
                    title = f"{title_prefix} to {last_stop.stop_name}"
                else:
                    title = f"To {last_stop.stop_name}"

            print(f'  title = {title}')

            dir_data = {
                'id': id,
                'title': title,
                'gtfs_shape_id': gtfs_shape_id,
                'gtfs_direction_id': gtfs_direction_id,
                'stops': stop_ids,
                'stop_geometry': {},
            }
            route_data['directions'].append(dir_data)

            for stop_id in stop_ids:
                stop = stops_map[stop_id]
                stop_data = {
                    'id': stop_id,
                    'lat': round(stop.geometry.y, 5), # stop_lat in gtfs
                    'lon': round(stop.geometry.x, 5), # stop_lon in gtfs
                    'title': stop.stop_name,
                    'url': stop.stop_url if hasattr(stop, 'stop_url') and isinstance(stop.stop_url, str) else None,
                }
                route_data['stops'][stop_id] = stop_data

            geometry = shapes_df[shapes_df['shape_id'] == gtfs_shape_id]['geometry'].values[0]

            # partridge returns GTFS geometries for each shape_id as a shapely LineString
            # (https://shapely.readthedocs.io/en/stable/manual.html#linestrings).
            # Each coordinate is an array in [lon,lat] format (note: longitude first, latitude second)
            dir_data['coords'] = [
                {
                    'lat': round(coord[1], 5),
                    'lon': round(coord[0], 5)
                } for coord in geometry.coords
            ]

            if agency.provider == 'nextbus':
                # match nextbus direction IDs with GTFS direction IDs
                best_nextbus_dir_info, best_terminal_dist = match_nextbus_direction(nextbus_route_config, geometry)
                print(f'  {direction_id} = {best_nextbus_dir_info.id} (terminal_dist={int(best_terminal_dist)}) {" (questionable match)" if best_terminal_dist > 300 else ""}')
                # dir_data['title'] = best_nextbus_dir_info.title
                dir_data['nextbus_direction_id'] = best_nextbus_dir_info.id

            start_lat = geometry.coords[0][1]
            start_lon = geometry.coords[0][0]

            #print(f"  start_lat = {start_lat} start_lon = {start_lon}")

            deg_lat_dist = util.haver_distance(start_lat, start_lon, start_lat-0.1, start_lon)*10
            deg_lon_dist = util.haver_distance(start_lat, start_lon, start_lat, start_lon-0.1)*10

            # projection function from lon/lat coordinates in degrees (z ignored) to x/y coordinates in meters.
            # satisfying the interface of shapely.ops.transform (https://shapely.readthedocs.io/en/stable/manual.html#shapely.ops.transform).
            # This makes it possible to use shapely methods to calculate the distance in meters between geometries
            def project_xy(lon, lat, z=None):
                return (round((lon - start_lon) * deg_lon_dist, 1), round((lat - start_lat) * deg_lat_dist, 1))

            xy_geometry = shapely.ops.transform(project_xy, geometry)

            shape_lon_lat = np.array(geometry).T
            shape_lon = shape_lon_lat[0]
            shape_lat = shape_lon_lat[1]

            shape_prev_lon = np.r_[shape_lon[0], shape_lon[:-1]]
            shape_prev_lat = np.r_[shape_lat[0], shape_lat[:-1]]

            # shape_cumulative_dist[i] is the cumulative distance in meters along the shape geometry from 0th to ith coordinate
            shape_cumulative_dist = np.cumsum(util.haver_distance(shape_lon, shape_lat, shape_prev_lon, shape_prev_lat))

            shape_lines_xy = [shapely.geometry.LineString(xy_geometry.coords[i:i+2]) for i in range(0, len(xy_geometry.coords) - 1)]

            # this is the total distance of the GTFS shape, which may not be exactly the same as the
            # distance along the route between the first and last Nextbus stop
            dir_data['distance'] = int(shape_cumulative_dist[-1])

            print(f"  distance = {dir_data['distance']}")

            # Find each stop along the route shape, so that the frontend can draw line segments between stops along the shape
            start_index = 0

            for stop_id in stop_ids:
                stop_info = route_data['stops'][stop_id]

                # Need to project lon/lat coords to x/y in order for shapely to determine the distance between
                # a point and a line (shapely doesn't support distance for lon/lat coords)

                stop_xy = shapely.geometry.Point(project_xy(stop_info['lon'], stop_info['lat']))

                stop_geometry = get_stop_geometry(stop_xy, shape_lines_xy, shape_cumulative_dist, start_index)

                if stop_geometry['offset'] > 100:
                    print(f"    !! bad geometry for stop {stop_id}: {stop_geometry['offset']} m from route line segment")
                    continue

                dir_data['stop_geometry'][stop_id] = stop_geometry

                start_index = stop_geometry['after_index']