Skip to content

Instantly share code, notes, and snippets.

@kuanb
Last active May 26, 2023 09:33
Show Gist options
  • Save kuanb/755ba136ff9ec0bea24ca4962a33168c to your computer and use it in GitHub Desktop.
Save kuanb/755ba136ff9ec0bea24ca4962a33168c to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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