Skip to content

Instantly share code, notes, and snippets.

@deton
Last active October 19, 2025 07:56
Show Gist options
  • Select an option

  • Save deton/8c4bd41faa4060dfa4a68066a5403be3 to your computer and use it in GitHub Desktop.

Select an option

Save deton/8c4bd41faa4060dfa4a68066a5403be3 to your computer and use it in GitHub Desktop.
Display GTFS stop_times.txt using gtfs_kit and kepler.gl Trip Layer, pydeck TripsLayer, or folium TimestampedGeoJson
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Display GTFS stop_times.txt using kepler.gl Trip Layer\n",
"\n",
"https://colab.research.google.com/drive/1FG11JhOyMP1Ol97ZBImKfnoEAEELdplx?usp=sharing"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install gtfs_kit keplergl"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>indicator</th>\n",
" <th>value</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>agencies</td>\n",
" <td>[ハマちゃんバス]</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>timezone</td>\n",
" <td>Asia/Tokyo</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>start_date</td>\n",
" <td>20230601</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>end_date</td>\n",
" <td>20260331</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>num_routes</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>num_trips</td>\n",
" <td>12</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>num_stops</td>\n",
" <td>31</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>num_shapes</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>sample_date</td>\n",
" <td>20230608</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>num_routes_active_on_sample_date</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>num_trips_active_on_sample_date</td>\n",
" <td>12</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>num_stops_active_on_sample_date</td>\n",
" <td>20</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" indicator value\n",
"0 agencies [ハマちゃんバス]\n",
"1 timezone Asia/Tokyo\n",
"2 start_date 20230601\n",
"3 end_date 20260331\n",
"4 num_routes 3\n",
"5 num_trips 12\n",
"6 num_stops 31\n",
"7 num_shapes 3\n",
"8 sample_date 20230608\n",
"9 num_routes_active_on_sample_date 3\n",
"10 num_trips_active_on_sample_date 12\n",
"11 num_stops_active_on_sample_date 20"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import gtfs_kit as gk\n",
"\n",
"# https://bustime.jp/GtfsAgency/gtfs_list?p1=14\n",
"url = 'https://api.gtfs-data.jp/v2/organizations/yokosukacity/feeds/hamachanbus/files/feed.zip'\n",
"feed = gk.read_feed(url, dist_units='m')\n",
"feed.describe()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"week = feed.get_first_week()\n",
"date = week[0] # Monday"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>stop_id</th>\n",
" <th>stop_name</th>\n",
" <th>platform_code</th>\n",
" <th>stop_lat</th>\n",
" <th>stop_lon</th>\n",
" <th>zone_id</th>\n",
" <th>location_type</th>\n",
" <th>parent_station</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>S1_1</td>\n",
" <td>追浜東団地集会所</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.309189</td>\n",
" <td>139.629466</td>\n",
" <td>S1_1</td>\n",
" <td>0</td>\n",
" <td>S1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>S1_2</td>\n",
" <td>追浜東団地集会所</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.309189</td>\n",
" <td>139.629466</td>\n",
" <td>S1_2</td>\n",
" <td>0</td>\n",
" <td>S1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>S2_1</td>\n",
" <td>パークハウス</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.310473</td>\n",
" <td>139.628737</td>\n",
" <td>S2_1</td>\n",
" <td>0</td>\n",
" <td>S2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>S2_2</td>\n",
" <td>パークハウス</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.310473</td>\n",
" <td>139.628737</td>\n",
" <td>S2_2</td>\n",
" <td>0</td>\n",
" <td>S2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>S3_1</td>\n",
" <td>浜見台1丁目</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.309822</td>\n",
" <td>139.626619</td>\n",
" <td>S3_1</td>\n",
" <td>0</td>\n",
" <td>S3</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" stop_id stop_name platform_code stop_lat stop_lon zone_id \\\n",
"1 S1_1 追浜東団地集会所 <NA> 35.309189 139.629466 S1_1 \n",
"2 S1_2 追浜東団地集会所 <NA> 35.309189 139.629466 S1_2 \n",
"4 S2_1 パークハウス <NA> 35.310473 139.628737 S2_1 \n",
"5 S2_2 パークハウス <NA> 35.310473 139.628737 S2_2 \n",
"7 S3_1 浜見台1丁目 <NA> 35.309822 139.626619 S3_1 \n",
"\n",
" location_type parent_station \n",
"1 0 S1 \n",
"2 0 S1 \n",
"4 0 S2 \n",
"5 0 S2 \n",
"7 0 S3 "
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dfstops = feed.get_stops(date)\n",
"dfstops.head()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>trip_id</th>\n",
" <th>arrival_time</th>\n",
" <th>departure_time</th>\n",
" <th>stop_id</th>\n",
" <th>stop_sequence</th>\n",
" <th>stop_headsign</th>\n",
" <th>pickup_type</th>\n",
" <th>drop_off_type</th>\n",
" <th>stop_name</th>\n",
" <th>platform_code</th>\n",
" <th>stop_lat</th>\n",
" <th>stop_lon</th>\n",
" <th>zone_id</th>\n",
" <th>location_type</th>\n",
" <th>parent_station</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>R3+0+平日+1</td>\n",
" <td>10:29:00</td>\n",
" <td>10:29:00</td>\n",
" <td>S1_1</td>\n",
" <td>1</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>追浜東団地集会所</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.309189</td>\n",
" <td>139.629466</td>\n",
" <td>S1_1</td>\n",
" <td>0</td>\n",
" <td>S1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>R3+0+平日+1</td>\n",
" <td>10:32:00</td>\n",
" <td>10:32:00</td>\n",
" <td>S3_1</td>\n",
" <td>2</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>浜見台1丁目</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.309822</td>\n",
" <td>139.626619</td>\n",
" <td>S3_1</td>\n",
" <td>0</td>\n",
" <td>S3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>R3+0+平日+1</td>\n",
" <td>10:34:00</td>\n",
" <td>10:34:00</td>\n",
" <td>S4_1</td>\n",
" <td>3</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>浜見台2丁目A</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.306984</td>\n",
" <td>139.628112</td>\n",
" <td>S4_1</td>\n",
" <td>0</td>\n",
" <td>S4</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>R3+0+平日+1</td>\n",
" <td>10:35:00</td>\n",
" <td>10:35:00</td>\n",
" <td>S5_1</td>\n",
" <td>4</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>浜見台2丁目B</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.306837</td>\n",
" <td>139.629605</td>\n",
" <td>S5_1</td>\n",
" <td>0</td>\n",
" <td>S5</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>R3+0+平日+1</td>\n",
" <td>10:36:00</td>\n",
" <td>10:36:00</td>\n",
" <td>S6_1</td>\n",
" <td>5</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>ケアセンター</td>\n",
" <td>&lt;NA&gt;</td>\n",
" <td>35.308191</td>\n",
" <td>139.630542</td>\n",
" <td>S6_1</td>\n",
" <td>0</td>\n",
" <td>S6</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" trip_id arrival_time departure_time stop_id stop_sequence stop_headsign \\\n",
"0 R3+0+平日+1 10:29:00 10:29:00 S1_1 1 <NA> \n",
"1 R3+0+平日+1 10:32:00 10:32:00 S3_1 2 <NA> \n",
"2 R3+0+平日+1 10:34:00 10:34:00 S4_1 3 <NA> \n",
"3 R3+0+平日+1 10:35:00 10:35:00 S5_1 4 <NA> \n",
"4 R3+0+平日+1 10:36:00 10:36:00 S6_1 5 <NA> \n",
"\n",
" pickup_type drop_off_type stop_name platform_code stop_lat stop_lon \\\n",
"0 0 1 追浜東団地集会所 <NA> 35.309189 139.629466 \n",
"1 0 0 浜見台1丁目 <NA> 35.309822 139.626619 \n",
"2 0 0 浜見台2丁目A <NA> 35.306984 139.628112 \n",
"3 0 0 浜見台2丁目B <NA> 35.306837 139.629605 \n",
"4 0 0 ケアセンター <NA> 35.308191 139.630542 \n",
"\n",
" zone_id location_type parent_station \n",
"0 S1_1 0 S1 \n",
"1 S3_1 0 S3 \n",
"2 S4_1 0 S4 \n",
"3 S5_1 0 S5 \n",
"4 S6_1 0 S6 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"dfst = feed.get_stop_times(date)\n",
"dfst = dfst.merge(dfstops)\n",
"display(dfst.head())\n",
"tripsdict = dfst.groupby('trip_id').apply(lambda x: x.to_dict(orient='records'), include_groups=False).to_dict()\n",
"#tripsdict"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"import datetime\n",
"datesec = int(datetime.datetime.fromisoformat(date).timestamp())\n",
"features = []\n",
"for trip_id, stop_times_list in tripsdict.items():\n",
" coords = []\n",
" for st in stop_times_list:\n",
" timesec = datesec + gk.helpers.timestr_to_seconds(st['arrival_time'])\n",
" # lon,lat,alt,timestamp for kepler.gl Trip Layer\n",
" coords.append([st['stop_lon'], st['stop_lat'], 0, timesec])\n",
" if st.get('departure_time') and st['departure_time'] != st['arrival_time']:\n",
" timesec = datesec + gk.helpers.timestr_to_seconds(st['departure_time'])\n",
" coords.append([st['stop_lon'], st['stop_lat'], 0, timesec])\n",
" coords.sort(key=lambda x: x[3]) # sort by timesec\n",
" features.append({\n",
" 'type': 'Feature',\n",
" 'properties': {\n",
" 'trip_id': trip_id,\n",
" },\n",
" 'geometry': {\n",
" 'type': 'LineString',\n",
" 'coordinates': coords,\n",
" }\n",
" })\n",
"geojson = {\n",
" 'type': 'FeatureCollection',\n",
" 'features': features,\n",
"}\n",
"#geojson"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"User Guide: https://docs.kepler.gl/docs/keplergl-jupyter\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "3442b8042c7d4771b14b6d9561cf6495",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"KeplerGl(data={'stops': stop_id stop_name platform_code stop_lat stop_lon zone_id \\\n",
"1 S1_1 追浜…"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from keplergl import KeplerGl\n",
"map_1 = KeplerGl(height=600, data={\"stops\": dfstops, \"trips\": geojson})\n",
"map_1"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Map saved to keplerglTrip.html!\n"
]
}
],
"source": [
"map_1.save_to_html(file_name='keplerglTrip.html')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment