Last active
December 7, 2024 18:11
-
-
Save kuanb/755ba136ff9ec0bea24ca4962a33168c to your computer and use it in GitHub Desktop.
Pairs with blog post http://kuanbutts.com/2020/09/12/raptor-simple-example/
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
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"DEBUG:root:test\n" | |
] | |
} | |
], | |
"source": [ | |
"import logging\n", | |
"import partridge as ptg\n", | |
"\n", | |
"# capture logs in notebook\n", | |
"logger = logging.getLogger()\n", | |
"logger.setLevel(logging.DEBUG)\n", | |
"logging.debug(\"test\")\n", | |
"\n", | |
"# load a GTFS of AC Transit\n", | |
"path = 'gtfs.zip'\n", | |
"_date, service_ids = ptg.read_busiest_date(path)\n", | |
"view = {'trips.txt': {'service_id': service_ids}}\n", | |
"feed = ptg.load_feed(path, view)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [] | |
} | |
], | |
"source": [ | |
"import geopandas as gpd\n", | |
"import pyproj\n", | |
"from shapely.geometry import Point\n", | |
"\n", | |
"# convert all known stops in the schedule to shapes in a GeoDataFrame\n", | |
"gdf = gpd.GeoDataFrame(\n", | |
" {\"stop_id\": feed.stops.stop_id.tolist()},\n", | |
" geometry=[\n", | |
" Point(lon, lat)\n", | |
" for lat, lon in zip(\n", | |
" feed.stops.stop_lat,\n", | |
" feed.stops.stop_lon)])\n", | |
"gdf = gdf.set_index(\"stop_id\")\n", | |
"gdf.crs = {'init': 'epsg:4326'}\n", | |
"\n", | |
"# re-cast to meter-based projection to allow for distance calculations\n", | |
"aeqd = pyproj.Proj(\n", | |
" proj='aeqd',\n", | |
" ellps='WGS84',\n", | |
" datum='WGS84',\n", | |
" lat_0=gdf.iloc[0].geometry.centroid.y,\n", | |
" lon_0=gdf.iloc[0].geometry.centroid.x).srs\n", | |
"gdf = gdf.to_crs(crs=aeqd)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [], | |
"source": [ | |
"# let's use this example origin and destination\n", | |
"# to find the time it would take to go from one to another\n", | |
"from_stop_name = \"Santa Clara Av & Mozart St\"\n", | |
"to_stop_name = \"10th Avenue SB\"\n", | |
"\n", | |
"# QA: we know the best way to connect these two is the 51A -> 1T\n", | |
"# if we depart at 8:30 AM, schedule should suggest:\n", | |
"# take 51A 8:37 - 8:49\n", | |
"# make walk connection\n", | |
"# take 1T 8:56 - 9:03\n", | |
"# total travel time: 26 minutes\n", | |
"\n", | |
"# look at all trips from that stop that are after the depart time\n", | |
"departure_secs = 8.5 * 60 * 60\n", | |
"\n", | |
"# get all information, including the stop ids, for the start and end nodes\n", | |
"from_stop = feed.stops[feed.stops.stop_name == from_stop_name].head(1).squeeze()\n", | |
"to_stop = feed.stops[[\"10th Avenue\" in f for f in feed.stops.stop_name]].head(1).squeeze()\n", | |
"\n", | |
"# extract just the stop ids\n", | |
"from_stop_id = from_stop.stop_id\n", | |
"to_stop_id = to_stop.stop_id" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from copy import copy\n", | |
"from typing import Any\n", | |
"from typing import Dict\n", | |
"from typing import List\n", | |
"\n", | |
"# assume all xfers are 3 minutes\n", | |
"TRANSFER_COST = (5 * 60)\n", | |
"\n", | |
"\n", | |
"def get_trip_ids_for_stop(feed, stop_id: str, departure_time: int):\n", | |
" \"\"\"Takes a stop and departure time and get associated trip ids.\"\"\"\n", | |
" mask_1 = feed.stop_times.stop_id == stop_id\n", | |
" mask_2 = feed.stop_times.departure_time >= departure_time\n", | |
"\n", | |
" # extract the list of qualifying trip ids\n", | |
" potential_trips = feed.stop_times[mask_1 & mask_2].trip_id.unique().tolist()\n", | |
" return potential_trips\n", | |
"\n", | |
"\n", | |
"def stop_times_for_kth_trip(\n", | |
" from_stop_id: str,\n", | |
" stop_ids: List[str],\n", | |
" time_to_stops_orig: Dict[str, Any],\n", | |
") -> Dict[str, Any]:\n", | |
" # prevent upstream mutation of dictionary\n", | |
" time_to_stops = copy(time_to_stops_orig)\n", | |
" stop_ids = list(stop_ids)\n", | |
"\n", | |
" for i, ref_stop_id in enumerate(stop_ids):\n", | |
" # how long it took to get to the stop so far (0 for start node)\n", | |
" baseline_cost = time_to_stops[ref_stop_id]\n", | |
"\n", | |
" # get list of all trips associated with this stop\n", | |
" potential_trips = get_trip_ids_for_stop(feed, ref_stop_id, departure_secs)\n", | |
" for potential_trip in potential_trips:\n", | |
"\n", | |
" # get all the stop time arrivals for that trip\n", | |
" stop_times_sub = feed.stop_times[feed.stop_times.trip_id == potential_trip]\n", | |
" stop_times_sub = stop_times_sub.sort_values(by=\"stop_sequence\")\n", | |
"\n", | |
" # get the \"hop on\" point\n", | |
" from_her_subset = stop_times_sub[stop_times_sub.stop_id == ref_stop_id]\n", | |
" from_here = from_her_subset.head(1).squeeze()\n", | |
"\n", | |
" # get all following stops\n", | |
" stop_times_after_mask = stop_times_sub.stop_sequence >= from_here.stop_sequence\n", | |
" stop_times_after = stop_times_sub[stop_times_after_mask]\n", | |
"\n", | |
" # for all following stops, calculate time to reach\n", | |
" arrivals_zip = zip(stop_times_after.arrival_time, stop_times_after.stop_id)\n", | |
" for arrive_time, arrive_stop_id in arrivals_zip:\n", | |
" \n", | |
" # time to reach is diff from start time to arrival (plus any baseline cost)\n", | |
" arrive_time_adjusted = arrive_time - departure_secs + baseline_cost\n", | |
"\n", | |
" # only update if does not exist yet or is faster\n", | |
" if arrive_stop_id in time_to_stops:\n", | |
" if time_to_stops[arrive_stop_id] > arrive_time_adjusted:\n", | |
" time_to_stops[arrive_stop_id] = arrive_time_adjusted\n", | |
" else:\n", | |
" time_to_stops[arrive_stop_id] = arrive_time_adjusted\n", | |
"\n", | |
" return time_to_stops\n", | |
"\n", | |
"\n", | |
"def add_footpath_transfers(\n", | |
" stop_ids: List[str],\n", | |
" time_to_stops_orig: Dict[str, Any],\n", | |
" stops_gdf: gpd.GeoDataFrame,\n", | |
" transfer_cost=TRANSFER_COST,\n", | |
") -> Dict[str, Any]:\n", | |
" # prevent upstream mutation of dictionary\n", | |
" time_to_stops = copy(time_to_stops_orig)\n", | |
" stop_ids = list(stop_ids)\n", | |
"\n", | |
" # add in transfers to nearby stops\n", | |
" for stop_id in stop_ids:\n", | |
" stop_pt = stops_gdf.loc[stop_id].geometry\n", | |
"\n", | |
" # TODO: parameterize? transfer within .2 miles\n", | |
" meters_in_miles = 1610\n", | |
" qual_area = stop_pt.buffer(meters_in_miles/5)\n", | |
" \n", | |
" # get all stops within a short walk of target stop\n", | |
" mask = stops_gdf.intersects(qual_area)\n", | |
"\n", | |
" # time to reach new nearby stops is the transfer cost plus arrival at last stop\n", | |
" arrive_time_adjusted = time_to_stops[stop_id] + TRANSFER_COST\n", | |
"\n", | |
" # only update if currently inaccessible or faster than currrent option\n", | |
" for arrive_stop_id, row in stops_gdf[mask].iterrows():\n", | |
" if arrive_stop_id in time_to_stops:\n", | |
" if time_to_stops[arrive_stop_id] > arrive_time_adjusted:\n", | |
" time_to_stops[arrive_stop_id] = arrive_time_adjusted\n", | |
" else:\n", | |
" time_to_stops[arrive_stop_id] = arrive_time_adjusted\n", | |
" \n", | |
" return time_to_stops" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"INFO:root:\n", | |
"Analyzing possibilities with 0 transfers\n", | |
"INFO:root:\tinital qualifying stop ids count: 1\n", | |
"INFO:root:\tstop times calculated in 0.8255 seconds\n", | |
"INFO:root:\t\t28 stop ids added\n", | |
"INFO:root:\tfootpath transfers calculated in 0.6502 seconds\n", | |
"INFO:root:\t\t103 stop ids added\n", | |
"INFO:root:\n", | |
"Analyzing possibilities with 1 transfers\n", | |
"INFO:root:\tinital qualifying stop ids count: 132\n", | |
"INFO:root:\tstop times calculated in 137.7573 seconds\n", | |
"INFO:root:\t\t828 stop ids added\n", | |
"INFO:root:\tfootpath transfers calculated in 20.8401 seconds\n", | |
"INFO:root:\t\t1053 stop ids added\n", | |
"INFO:root:Time to destination: 35.5 minutes\n" | |
] | |
} | |
], | |
"source": [ | |
"import time\n", | |
"\n", | |
"# initialize lookup with start node taking 0 seconds to reach\n", | |
"time_to_stops = {from_stop_id: 0}\n", | |
"\n", | |
"# setting transfer limit at 1\n", | |
"TRANSFER_LIMIT = 1\n", | |
"for k in range(TRANSFER_LIMIT + 1):\n", | |
" logger.info(\"\\nAnalyzing possibilities with {} transfers\".format(k))\n", | |
" \n", | |
" # generate current list of stop ids under consideration\n", | |
" stop_ids = list(time_to_stops.keys())\n", | |
" logger.info(\"\\tinital qualifying stop ids count: {}\".format(len(stop_ids)))\n", | |
" \n", | |
" # update time to stops calculated based on stops accessible\n", | |
" tic = time.perf_counter()\n", | |
" time_to_stops = stop_times_for_kth_trip(from_stop_id, stop_ids, time_to_stops)\n", | |
" toc = time.perf_counter()\n", | |
" logger.info(\"\\tstop times calculated in {:0.4f} seconds\".format(toc - tic))\n", | |
"\n", | |
" added_keys_count = len((time_to_stops.keys())) - len(stop_ids)\n", | |
" logger.info(\"\\t\\t{} stop ids added\".format(added_keys_count))\n", | |
" \n", | |
" # now add footpath transfers and update\n", | |
" tic = time.perf_counter()\n", | |
" stop_ids = list(time_to_stops.keys())\n", | |
" time_to_stops = add_footpath_transfers(stop_ids, time_to_stops, gdf)\n", | |
" toc = time.perf_counter()\n", | |
" logger.info(\"\\tfootpath transfers calculated in {:0.4f} seconds\".format(toc - tic))\n", | |
"\n", | |
" added_keys_count = len((time_to_stops.keys())) - len(stop_ids)\n", | |
" logger.info(\"\\t\\t{} stop ids added\".format(added_keys_count))\n", | |
" \n", | |
"assert to_stop_id in time_to_stops, \"Unable to find route to destination within transfer limit\"\n", | |
"logger.info(\"Time to destination: {} minutes\".format(time_to_stops[to_stop_id]/60))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.6.6" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment