375 lines
32 KiB
Plaintext
375 lines
32 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "ca04f457-164b-447a-8a7e-7864892c221b",
|
|
"metadata": {
|
|
"collapsed": true,
|
|
"jupyter": {
|
|
"outputs_hidden": true
|
|
}
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: geopy in /home/arfox/.local/lib/python3.12/site-packages (2.4.1)\n",
|
|
"Requirement already satisfied: geographiclib<3,>=1.52 in /home/arfox/.local/lib/python3.12/site-packages (from geopy) (2.1)\n",
|
|
"Note: you may need to restart the kernel to use updated packages.\n",
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: coloraide in /home/arfox/.local/lib/python3.12/site-packages (6.2)\n",
|
|
"Note: you may need to restart the kernel to use updated packages.\n",
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: matplotlib in /home/arfox/.local/lib/python3.12/site-packages (3.8.4)\n",
|
|
"Requirement already satisfied: contourpy>=1.0.1 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (1.3.1)\n",
|
|
"Requirement already satisfied: cycler>=0.10 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (0.12.1)\n",
|
|
"Requirement already satisfied: fonttools>=4.22.0 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (4.56.0)\n",
|
|
"Requirement already satisfied: kiwisolver>=1.3.1 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (1.4.8)\n",
|
|
"Requirement already satisfied: numpy>=1.21 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (1.26.4)\n",
|
|
"Requirement already satisfied: packaging>=20.0 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (23.2)\n",
|
|
"Requirement already satisfied: pillow>=8 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (11.1.0)\n",
|
|
"Requirement already satisfied: pyparsing>=2.3.1 in /home/arfox/.local/lib/python3.12/site-packages (from matplotlib) (3.2.1)\n",
|
|
"Requirement already satisfied: python-dateutil>=2.7 in /opt/miniconda3/envs/jupyter/lib/python3.12/site-packages (from matplotlib) (2.9.0.post0)\n",
|
|
"Requirement already satisfied: six>=1.5 in /opt/miniconda3/envs/jupyter/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)\n",
|
|
"Note: you may need to restart the kernel to use updated packages.\n",
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: requests in /home/arfox/.local/lib/python3.12/site-packages (2.32.3)\n",
|
|
"Requirement already satisfied: charset-normalizer<4,>=2 in /home/arfox/.local/lib/python3.12/site-packages (from requests) (3.4.1)\n",
|
|
"Requirement already satisfied: idna<4,>=2.5 in /home/arfox/.local/lib/python3.12/site-packages (from requests) (3.10)\n",
|
|
"Requirement already satisfied: urllib3<3,>=1.21.1 in /home/arfox/.local/lib/python3.12/site-packages (from requests) (2.4.0)\n",
|
|
"Requirement already satisfied: certifi>=2017.4.17 in /home/arfox/.local/lib/python3.12/site-packages (from requests) (2025.1.31)\n",
|
|
"Note: you may need to restart the kernel to use updated packages.\n",
|
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
|
"Requirement already satisfied: scipy in /home/arfox/.local/lib/python3.12/site-packages (1.15.2)\n",
|
|
"Requirement already satisfied: numpy<2.5,>=1.23.5 in /home/arfox/.local/lib/python3.12/site-packages (from scipy) (1.26.4)\n",
|
|
"Note: you may need to restart the kernel to use updated packages.\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"%pip install geopy\n",
|
|
"%pip install coloraide\n",
|
|
"%pip install matplotlib\n",
|
|
"%pip install requests\n",
|
|
"%pip install scipy"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "839b8a06-f643-4896-b9db-5640d83d5511",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import json\n",
|
|
"from IPython.display import JSON, display, HTML\n",
|
|
"from datetime import datetime\n",
|
|
"from dataclasses import dataclass\n",
|
|
"from geopy.geocoders import Nominatim\n",
|
|
"from coloraide import Color\n",
|
|
"from scipy import stats\n",
|
|
"from collections import OrderedDict\n",
|
|
"import requests\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import numpy as np"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b47d4ce9-0bfe-4db1-b5f0-a033034f70c2",
|
|
"metadata": {},
|
|
"source": [
|
|
"I self host [Nominatim](https://nominatim.org/) with New York State loaded, it took forever to load. If you want to self host, I'd recommend you get the NYC and surrounding area from Interline: https://app.interline.io/osm_extracts/interactive_view\n",
|
|
"\n",
|
|
"If I remember correctly, the default Nominatim configuration connects to a free public server that requests you stay under one geocoding request per second."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"id": "7b42f081-fac6-48f9-9c87-6b4782ebe15b",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# geolocator = Nominatim(\n",
|
|
"# domain=\"192.168.68.131:8088\",\n",
|
|
"# scheme=\"http\",\n",
|
|
"# user_agent=\"apt-hunt-2026\",\n",
|
|
"# timeout=5 # My instance of Nominatim is slow and the default timeout is only one second\n",
|
|
"# )\n",
|
|
"\n",
|
|
"# user_agent can basically be anything, but if you remove it it'll yell at you to pick something to identify yourself\n",
|
|
"geolocator = Nominatim(\n",
|
|
" user_agent=\"apt-hunt-2026\",\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3880b549-c73c-4aa1-8524-02ef67fa2a7a",
|
|
"metadata": {},
|
|
"source": [
|
|
"I'm guessing you might know this part better than me, the routing library uses GraphQL and I only have a cursory understanding of it and haven't learned how to use any of the libraries like [Strawberry](https://strawberry.rocks/). I'm also not positive I have all the right modes of transit listed in this query yet."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "406fe6a5-c933-4324-a2e7-a176a0dfdb11",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def build_query(origin_lat: float, origin_lon: float,\n",
|
|
" dest_lat: float, dest_lon: float,\n",
|
|
" earliest_departure: str = \"2026-01-05T9:30-05:00\") -> str:\n",
|
|
" query_template = f\"\"\"\n",
|
|
"query {{\n",
|
|
" planConnection(\n",
|
|
" origin: {{\n",
|
|
" location: {{ coordinate: {{ latitude: {origin_lat}, longitude: {origin_lon} }} }}\n",
|
|
" }}\n",
|
|
" destination: {{\n",
|
|
" location: {{ coordinate: {{ latitude: {dest_lat}, longitude: {dest_lon} }} }}\n",
|
|
" }}\n",
|
|
" dateTime: {{ earliestDeparture: \"{earliest_departure}\" }}\n",
|
|
" modes: {{\n",
|
|
" direct: [WALK]\n",
|
|
" transit: {{ transit: [{{ mode: BUS }}, {{ mode: RAIL }}, {{ mode: SUBWAY }}] }}\n",
|
|
" }}\n",
|
|
" ) {{\n",
|
|
" edges {{\n",
|
|
" node {{\n",
|
|
" start\n",
|
|
" end\n",
|
|
" legs {{\n",
|
|
" mode\n",
|
|
" from {{\n",
|
|
" name\n",
|
|
" lat\n",
|
|
" lon\n",
|
|
" departure {{\n",
|
|
" scheduledTime\n",
|
|
" estimated {{\n",
|
|
" time\n",
|
|
" delay\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" to {{\n",
|
|
" name\n",
|
|
" lat\n",
|
|
" lon\n",
|
|
" arrival {{\n",
|
|
" scheduledTime\n",
|
|
" estimated {{\n",
|
|
" time\n",
|
|
" delay\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" route {{\n",
|
|
" gtfsId\n",
|
|
" longName\n",
|
|
" shortName\n",
|
|
" }}\n",
|
|
" legGeometry {{\n",
|
|
" points\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
" }}\n",
|
|
"}}\n",
|
|
"\"\"\"\n",
|
|
" \n",
|
|
" return query_template.strip()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "35c24bc5-fd6d-49ce-a835-2c3b0285c617",
|
|
"metadata": {},
|
|
"source": [
|
|
"I self host [OpenTripPlanner](https://docs.opentripplanner.org/en/latest/), I'm not sure if there are any easy public instances like Nominatim has. I can set up some auth for mine if you want to connect to it, right now it's only available on my home network. And it looks like PATH might be a bit more of a pain than the other transit networks, hopefully I'll have that one figured out soon."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "6e8d4cb0-735f-41b7-a765-26ce9dc56acd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def execute_plan_query(from_addr, to_addr):\n",
|
|
" url = \"http://192.168.68.138:8080/otp/gtfs/v1\"\n",
|
|
" headers = {\"Content-Type\": \"application/json\"}\n",
|
|
" \n",
|
|
" query = build_query(\n",
|
|
" origin_lat=from_addr.latitude,\n",
|
|
" origin_lon=from_addr.longitude,\n",
|
|
" dest_lat=to_addr.latitude,\n",
|
|
" dest_lon=to_addr.longitude,\n",
|
|
" earliest_departure=\"2026-01-012T15:30-05:00\"\n",
|
|
" )\n",
|
|
"\n",
|
|
" response = requests.post(\n",
|
|
" url,\n",
|
|
" json={\"query\": query},\n",
|
|
" headers=headers\n",
|
|
" )\n",
|
|
" \n",
|
|
" return response.json()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "42e41f8e-8692-4b95-a4cd-242074a1ec30",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"query = {\n",
|
|
" \"street\": \"330 34th Street\",\n",
|
|
" \"city\": \"NEW YORK\",\n",
|
|
" \"county\": \"New York\",\n",
|
|
" \"state\": \"NY\",\n",
|
|
" \"postalcode\": \"10001\",\n",
|
|
"}\n",
|
|
"jfk36 = geolocator.geocode(query, timeout=5)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 93,
|
|
"id": "a7d72437-cb11-47db-b5d8-c6971836a8cd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"destinations = OrderedDict([\n",
|
|
" (\"JFK36\", jfk36),\n",
|
|
"])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 94,
|
|
"id": "00fed8bd-25cc-44d8-ab39-56a8e3425a5b",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"query = {\n",
|
|
" \"street\": \"373 Washington Ave\",\n",
|
|
" \"city\": \"NEW YORK\",\n",
|
|
" \"county\": \"Brooklyn\",\n",
|
|
" \"state\": \"NY\",\n",
|
|
" \"postalcode\": \"11238\",\n",
|
|
"}\n",
|
|
"home = geolocator.geocode(query)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 95,
|
|
"id": "b1fb109b-d7be-4566-994c-74805ff66e4a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"all_trips = {}\n",
|
|
"\n",
|
|
"all_starts = {\n",
|
|
" \"Home\": home,\n",
|
|
"}\n",
|
|
"\n",
|
|
"for label, start_loc in all_starts.items():\n",
|
|
" all_trips[label] = []\n",
|
|
" \n",
|
|
" for _, dest in destinations.items():\n",
|
|
" trips = execute_plan_query(start_loc, dest)['data']['planConnection']['edges']\n",
|
|
" \n",
|
|
" trip_durations = [\n",
|
|
" (datetime.fromisoformat(trip['node']['end']) - datetime.fromisoformat(trip['node']['start']))\n",
|
|
" for trip in trips\n",
|
|
" ]\n",
|
|
" trips_in_seconds = [trip.seconds for trip in trip_durations]\n",
|
|
" trips_in_minutes = [seconds / 60 for seconds in trips_in_seconds]\n",
|
|
" all_trips[label].append(trips_in_minutes)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 96,
|
|
"id": "858ed8a1-5727-4b2c-a482-57733a6eafdc",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAEiCAYAAAAWHJuuAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAN6tJREFUeJzt3Xm4VmW9P/73HmADMqqMyqCMMipahlqJkojKVyyHlHJKj6dwANRfUjmQJdZRK9MvDqV0KrX0q+nxiGYqdCxMQPGIOWEOHEVRExCHDez9/P7w8jnu4FE37M120+t1Xeu61nOve631eR65W+e8ublXWaFQKAQAAAAAAFhPeVMXAAAAAAAAn1RCdAAAAAAAKEGIDgAAAAAAJQjRAQAAAACgBCE6AAAAAACUIEQHAAAAAIAShOgAAAAAAFCCEB0AAAAAAEoQogMAAAAAQAlCdAAAAAAAKEGIDgAADWTWrFkpKyvLggULNnh87733ztChQzdzVQAAwKYQogMAAAAAQAlCdAAAAAAAKEGIDgAATWTdunU5//zz07dv31RVVaVPnz751re+lerq6jr9+vTpk4MOOihz5szJbrvtltatW2fYsGGZM2dOkuTmm2/OsGHD0qpVq+y66655+OGH17vXE088kUMPPTRbb711WrVqld122y233Xbb5viaAADQrAnRAQCgga1cuTKvvfbaetvatWvr9DvhhBNyzjnnZOTIkfnRj36Uz3/+85kxY0a+/OUvr3fNJUuW5Kijjsr48eMzY8aMvPHGGxk/fnx+/etfZ8qUKfnKV76S6dOn55lnnsnhhx+e2tra4rmPPfZYPvOZz+Txxx/PWWedlYsvvjhbbbVVJkyYkFtuuaXRfw8AAGjOygqFQqGpiwAAgC3BrFmzctxxx31onyFDhmTx4sV55JFHsvPOO+eEE07I1VdfXTx+5pln5qKLLsq9996b0aNHJ3lvJvrzzz+fP//5zxk1alSS5Pe//33Gjh2b1q1b54knnkivXr2SJFdddVVOOumk3Hfffdl7772TJGPGjMny5cszf/78VFVVJUkKhUL22muvvPrqq3nqqaca+qcAAIAthpnoAADQwC6//PLcfffd623Dhw8v9rnjjjuSJFOnTq1z7umnn54k+c///M867YMHDy4G6Emy++67J0n22WefYoD+wfa//e1vSZK///3vuffee3P44YfnzTffLM6Kf/311zN27Ng8/fTTefHFFxvqqwMAwBansqkLAACALc2nP/3p7Lbbbuu1d+rUKa+99lqS5Pnnn095eXn69etXp0+3bt3SsWPHPP/883XaPxiUJ0mHDh2SJD179txg+xtvvJHkvWVgCoVCzj777Jx99tkbrHf58uXZbrvtPu7XAwCAfypCdAAAaEJlZWUfq19FRUW92t9ftfH9tdHPOOOMjB07doN9/zHIBwAA/pcQHQAAmkDv3r1TW1ubp59+OjvttFOx/ZVXXsmKFSvSu3fvBrnPjjvumCRp0aJFxowZ0yDXBACAfybWRAcAgCZwwAEHJEl+/OMf12m/5JJLkiQHHnhgg9ynS5cu2XvvvXPllVdm2bJl6x1/9dVXG+Q+AACwpTITHQAAmsCIESNyzDHH5KqrrsqKFSvy+c9/Pg8++GB+8YtfZMKECRk9enSD3evyyy/PXnvtlWHDhuXEE0/MjjvumFdeeSXz5s3L//zP/+SRRx5psHsBAMCWRogOAABN5Gc/+1l23HHHzJo1K7fccku6deuWadOm5dxzz23Q+wwePDgLFizI9OnTM2vWrLz++uvp0qVLdtlll5xzzjkNei8AANjSlBXef+MQAAAAAABQhzXRAQAAAACgBCE6AAAAAACUIEQHAAAAAIAShOgAAAAAAFCCEB0AAAAAAEoQogMAAAAAQAmVm/uGtbW1eemll9KuXbuUlZVt7tsDAAAAALCFKxQKefPNN9OjR4+Ul2/aXPLNHqK/9NJL6dmz5+a+LQAAAAAA/2SWLl2a7bfffpOusdlD9Hbt2iV5r/j27dtv7tsDsAWqrlmTMx+9PEnyb8MmpaqiZRNXBAAAADSlVatWpWfPnsU8elNs9hD9/SVc2rdvL0QHoEFU16xJy7atkrz3fBGiAwAAAEkaZElxLxYFAAAAAIAShOgAAAAAAFCCEB0AAAAAAEqo95roL774Yr75zW9m9uzZefvtt9OvX79ce+212W233RqjPgD4SC3KK3N6/yOL+wAAAGy6mpqarF27tqnLgA1q0aJFKioqNsu96pU0vPHGG9lzzz0zevTozJ49O507d87TTz+dTp06NVZ9APCRysvKM7Bdr6YuAwAAYItQKBTy8ssvZ8WKFU1dCnyojh07plu3bg3y8tAPU68Q/Qc/+EF69uyZa6+9tti2ww47NHhRAAAAAEDTeD9A79KlS9q0adPoASXUV6FQyNtvv53ly5cnSbp3796o96tXiH7bbbdl7NixOeywwzJ37txst912+cY3vpETTzyx5DnV1dWprq4ufl61atXGVwsAG7CuUJP/eu2RJMlntx2RyrLN88+5AAAAtjQ1NTXFAH2bbbZp6nKgpNatWydJli9fni5dujTq0i71erHo3/72t8ycOTP9+/fPXXfdla9//es59dRT84tf/KLkOTNmzEiHDh2KW8+ePTe5aAC2fJctuam4fZSa2ppcv/TuXL/07tTU1myG6gAAALZM76+B3qZNmyauBD7a+39OG3vt/nqF6LW1tRk5cmQuuOCC7LLLLvmXf/mXnHjiibniiitKnjNt2rSsXLmyuC1dunSTiwYAAAAAGo8lXGgONtef03qF6N27d8/gwYPrtO2000554YUXSp5TVVWV9u3b19kAAAAAAKA5qFeIvueee+bJJ5+s0/bUU0+ld+/eDVoUAAAAAAB8EtQrRJ8yZUoeeOCBXHDBBVmyZEmuu+66XHXVVZk0aVJj1QcAAAAA8JGOPfbYTJgwobhfVla23rZkyZL1+r7vpptuSqtWrXLxxRcnSWbOnJnhw4cXV9cYNWpUZs+evd59582bl3322SdbbbVV2rdvn8997nN55513GvW7flIsWbIk7dq1S8eOHdc7duONN2bQoEFp1apVhg0bljvuuGPzF9hA6hWif+pTn8ott9yS66+/PkOHDs3555+fH//4x5k4cWJj1QcAAAAAUG/7779/li1bVmfbYYcdNtj3Zz/7WSZOnJiZM2fm9NNPT5Jsv/32ufDCC7Nw4cIsWLAg++yzTw4++OA89thjxfPmzZuX/fffP/vtt18efPDBzJ8/PyeffHLKy+sVuzZLa9euzZFHHpnPfvaz6x3785//nCOPPDJf+9rX8vDDD2fChAmZMGFCFi9e3ASVbrp6/9c86KCD8uijj+bdd9/N448/nhNPPLEx6gIAAAAA2GhVVVXp1q1bna2iomK9fj/84Q9zyimn5IYbbshxxx1XbB8/fnwOOOCA9O/fPwMGDMj3v//9tG3bNg888ECxz5QpU3LqqafmrLPOypAhQzJw4MAcfvjhqaqqKlnX3nvvnVNOOSWTJ09Op06d0rVr11x99dV56623ctxxx6Vdu3bp16/ferPeFy9enHHjxqVt27bp2rVrvvrVr+a1114rHr/zzjuz1157pWPHjtlmm21y0EEH5Zlnnikef+6551JWVpabb745o0ePTps2bTJixIjMmzdvo37f73znOxk0aFAOP/zw9Y795Cc/yf77758zzzwzO+20U84///yMHDkyl112WbFPnz598r3vfS9HH3102rZtm969e+e2227Lq6++moMPPjht27bN8OHDs2DBgo2qryFt+X8lAsAWr7K8Mif3PTQn9z00leWVTV0OAADAFqe6Zk3JbW3tuo/dd03t2o/Vd3P55je/mfPPPz+33357DjnkkJL9ampqcsMNN+Stt97KqFGjkiTLly/PX/7yl3Tp0iV77LFHunbtms9//vO5//77P/K+v/jFL7LtttvmwQcfzCmnnJKvf/3rOeyww7LHHnvkoYceyn777ZevfvWrefvtt5MkK1asyD777JNddtklCxYsyJ133plXXnmlToD91ltvZerUqVmwYEHuueeelJeX55BDDkltbW2de3/729/OGWeckUWLFmXAgAE58sgjs27d//43LCsry6xZsz60/nvvvTc33nhjLr/88g0enzdvXsaMGVOnbezYsesF9j/60Y+y55575uGHH86BBx6Yr371qzn66KPzla98JQ899FD69u2bo48+OoVC4SN/08YkaQCg2asoK8/wDn2bugwAAIAt1imP/KjksaHtd8yp/Q4rfj790cvWC8vfN6Btz5wx4Kji52mPXZHV69ZfP/yqkd/chGrfc/vtt6dt27bFz+PGjcuNN95Y/Dx79uzceuutueeee7LPPvts8BqPPvpoRo0alXfffTdt27bNLbfcksGDBydJ/va3vyVJzjvvvFx00UXZeeed8+///u/Zd999s3jx4vTv379kbSNGjMh3vvOdJMm0adNy4YUXZtttty2u+nHOOedk5syZ+e///u985jOfyWWXXZZddtklF1xwQfEa11xzTXr27JmnnnoqAwYMyJe+9KU697jmmmvSuXPn/PWvf83QoUOL7WeccUYOPPDAJMn06dMzZMiQLFmyJIMGDUqSDBw4MB06dChZ++uvv55jjz02v/rVr9K+ffsN9nn55ZfTtWvXOm1du3bNyy+/XKftgAMOyEknnVTnO3/qU5/KYYe99+fpm9/8ZkaNGpVXXnkl3bp1K1lTYxOiAwAAAABbnNGjR2fmzJnFz1tttVWd48OHD89rr72Wc889N5/+9KfrBO7vGzhwYBYtWpSVK1fmpptuyjHHHJO5c+dm8ODBxRneJ510UnEZmF122SX33HNPrrnmmsyYMaNkbcOHDy/uV1RUZJtttsmwYcOKbe8H0MuXL0+SPPLII7nvvvs2WOMzzzyTAQMG5Omnn84555yTv/zlL3nttdeK9b3wwgt1QvQP3rt79+7F+7wfoj/xxBMl606SE088MUcddVQ+97nPfWi/j+ODtbz/nUv9DkJ0ANgE6wo1efDvf02SfHrrwaksW3+NOwAAADbeT0dMKXmsvKzuitEXDzu5ZN+ysrI6n2cM+ddNK+xDbLXVVunXr1/J49ttt11uuummjB49Ovvvv39mz56ddu3a1enTsmXL4jV23XXXzJ8/Pz/5yU9y5ZVXFgPo92emv2+nnXbKCy+88KG1tWjRos7nsrKyOm3v/07vB+GrV6/O+PHj84Mf/GC9a71fx/jx49O7d+9cffXV6dGjR2prazN06NCsWVN3eZwPu8/Hce+99+a2227LRRddlCQpFAqpra1NZWVlrrrqqhx//PHp1q1bXnnllTrnbWg2+YZq2dT6GoMQHYBmr6a2JrOevyNJsmvHgancwItiAAAA2HhVFS2bvG9j6N27d+bOnVsM0u+88871gvQPqq2tTXV1dZL3XozZo0ePPPnkk3X6PPXUUxk3blyD1jly5Mj8v//3/9KnT59UVq4f6b7++ut58sknc/XVV+ezn/1sknystdk3xrx581JTU1P8fOutt+YHP/hB/vznP2e77bZLkowaNSr33HNPJk+eXOx39913F9eTb268WBQAAAAA+KfVs2fPzJkzJ8uXL8/YsWOzatWqJO+tVf7HP/4xzz33XB599NFMmzYtc+bMycSJE5O8N0v6zDPPzKWXXpqbbropS5Ysydlnn50nnngiX/va1xq0xkmTJuXvf/97jjzyyMyfPz/PPPNM7rrrrhx33HGpqalJp06dss022+Sqq67KkiVLcu+992bq1Kkbda9BgwbllltuKXl8p512ytChQ4vbdtttl/Ly8gwdOjSdOnVKkpx22mm58847c/HFF+eJJ57IeeedlwULFuTkk0v/K4VPMiE6AAAAANDsvb+kyMbYfvvtM2fOnLz22mvFIH358uU5+uijM3DgwOy7776ZP39+7rrrrnzhC18onjd58uRMmzYtU6ZM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/9+FQmGTr1WvEH1jzJgxI9OnT1+vvWfPno19awCa2P/XBNf5Rc5roLtuvA+rt6F+EwAAAOCjvf766+nQocMmXaNeIfq2226bioqKvPLKK3XaX3nllXTr1m2D50ybNi1Tp04tfl6xYkV69+6dF154YZOLh38Wq1atSs+ePbN06dK0b9++qcuBZsG4gfozbqD+jBuoP+MG6s+4gfpbuXJlevXqla233nqTr1WvEL1ly5bZddddc88992TChAlJktra2txzzz05+eSTN3hOVVVVqqqq1mvv0KGDQQ/11L59e+MG6sm4gfozbqD+jBuoP+MG6s+4gforL6/Xa0E3qN7LuUydOjXHHHNMdtttt3z605/Oj3/847z11ls57rjjNrkYAAAAAAD4JKl3iH7EEUfk1VdfzTnnnJOXX345O++8c+688871XjYKAAAAAADN3Ua9WPTkk08uuXzLR6mqqsq55567wSVegA0zbqD+jBuoP+MG6s+4gfozbqD+jBuov4YcN2WFQqHQADUBAAAAAMAWZ9NXVQcAAAAAgC2UEB0AAAAAAEoQogMAAAAAQAmNEqLPmDEjn/rUp9KuXbt06dIlEyZMyJNPPlmnz7vvvptJkyZlm222Sdu2bfOlL30pr7zySmOUA83CzJkzM3z48LRv3z7t27fPqFGjMnv27OJxYwY+2oUXXpiysrJMnjy52GbsQF3nnXdeysrK6myDBg0qHjdmYMNefPHFfOUrX8k222yT1q1bZ9iwYVmwYEHxeKFQyDnnnJPu3bundevWGTNmTJ5++ukmrBiaVp8+fdZ73pSVlWXSpElJPG9gQ2pqanL22Wdnhx12SOvWrdO3b9+cf/75+eDrDD1vYH1vvvlmJk+enN69e6d169bZY489Mn/+/OLxhhg3jRKiz507N5MmTcoDDzyQu+++O2vXrs1+++2Xt956q9hnypQp+Y//+I/ceOONmTt3bl566aV88YtfbIxyoFnYfvvtc+GFF2bhwoVZsGBB9tlnnxx88MF57LHHkhgz8FHmz5+fK6+8MsOHD6/TbuzA+oYMGZJly5YVt/vvv794zJiB9b3xxhvZc88906JFi8yePTt//etfc/HFF6dTp07FPj/84Q9z6aWX5oorrshf/vKXbLXVVhk7dmzefffdJqwcms78+fPrPGvuvvvuJMlhhx2WxPMGNuQHP/hBZs6cmcsuuyyPP/54fvCDH+SHP/xhfvrTnxb7eN7A+k444YTcfffd+eUvf5lHH300++23X8aMGZMXX3wxSQONm8JmsHz58kKSwty5cwuFQqGwYsWKQosWLQo33nhjsc/jjz9eSFKYN2/e5igJmoVOnToVfvaznxkz8BHefPPNQv/+/Qt333134fOf/3zhtNNOKxQKnjewIeeee25hxIgRGzxmzMCGffOb3yzstddeJY/X1tYWunXrVvi3f/u3YtuKFSsKVVVVheuvv35zlAifeKeddlqhb9++hdraWs8bKOHAAw8sHH/88XXavvjFLxYmTpxYKBQ8b2BD3n777UJFRUXh9ttvr9M+cuTIwre//e0GGzebZU30lStXJkm23nrrJMnChQuzdu3ajBkzpthn0KBB6dWrV+bNm7c5SoJPtJqamtxwww156623MmrUKGMGPsKkSZNy4IEH1hkjiecNlPL000+nR48e2XHHHTNx4sS88MILSYwZKOW2227LbrvtlsMOOyxdunTJLrvskquvvrp4/Nlnn83LL79cZ+x06NAhu+++u7EDSdasWZNf/epXOf7441NWVuZ5AyXsscceueeee/LUU08lSR555JHcf//9GTduXBLPG9iQdevWpaamJq1atarT3rp169x///0NNm4qG6ziEmprazN58uTsueeeGTp0aJLk5ZdfTsuWLdOxY8c6fbt27ZqXX365sUuCT6xHH300o0aNyrvvvpu2bdvmlltuyeDBg7No0SJjBkq44YYb8tBDD9VZ7+x9njewvt133z2zZs3KwIEDs2zZskyfPj2f/exns3jxYmMGSvjb3/6WmTNnZurUqfnWt76V+fPn59RTT03Lli1zzDHHFMdH165d65xn7MB7fve732XFihU59thjk/i/0aCUs846K6tWrcqgQYNSUVGRmpqafP/738/EiROTxPMGNqBdu3YZNWpUzj///Oy0007p2rVrrr/++sybNy/9+vVrsHHT6CH6pEmTsnjx4jprbQIbNnDgwCxatCgrV67MTTfdlGOOOSZz585t6rLgE2vp0qU57bTTcvfdd6/3t87Ahr0/kylJhg8fnt133z29e/fOb3/727Ru3boJK4NPrtra2uy222654IILkiS77LJLFi9enCuuuCLHHHNME1cHn3w///nPM27cuPTo0aOpS4FPtN/+9rf59a9/neuuuy5DhgzJokWLMnny5PTo0cPzBj7EL3/5yxx//PHZbrvtUlFRkZEjR+bII4/MwoULG+wejbqcy8knn5zbb7899913X7bffvtie7du3bJmzZqsWLGiTv9XXnkl3bp1a8yS4BOtZcuW6devX3bdddfMmDEjI0aMyE9+8hNjBkpYuHBhli9fnpEjR6aysjKVlZWZO3duLr300lRWVqZr167GDnyEjh07ZsCAAVmyZInnDZTQvXv3DB48uE7bTjvtVFwK6f3x8corr9TpY+xA8vzzz+cPf/hDTjjhhGKb5w1s2JlnnpmzzjorX/7ylzNs2LB89atfzZQpUzJjxowknjdQSt++fTN37tysXr06S5cuzYMPPpi1a9dmxx13bLBx0ygheqFQyMknn5xbbrkl9957b3bYYYc6x3fddde0aNEi99xzT7HtySefzAsvvJBRo0Y1RknQLNXW1qa6utqYgRL23XffPProo1m0aFFx22233TJx4sTivrEDH2716tV55pln0r17d88bKGHPPffMk08+WaftqaeeSu/evZMkO+ywQ7p161Zn7KxatSp/+ctfjB3+6V177bXp0qVLDjzwwGKb5w1s2Ntvv53y8rpRXUVFRWpra5N43sBH2WqrrdK9e/e88cYbueuuu3LwwQc32LhplOVcJk2alOuuuy633npr2rVrV1xfpkOHDmndunU6dOiQr33ta5k6dWq23nrrtG/fPqecckpGjRqVz3zmM41REnziTZs2LePGjUuvXr3y5ptv5rrrrsucOXNy1113GTNQQrt27Yrv23jfVlttlW222abYbuxAXWeccUbGjx+f3r1756WXXsq5556bioqKHHnkkZ43UMKUKVOyxx575IILLsjhhx+eBx98MFdddVWuuuqqJElZWVkmT56c733ve+nfv3922GGHnH322enRo0cmTJjQtMVDE6qtrc21116bY445JpWV/xs/eN7Aho0fPz7f//7306tXrwwZMiQPP/xwLrnkkhx//PFJPG+glLvuuiuFQiEDBw7MkiVLcuaZZ2bQoEE57rjjGm7cFBpBkg1u1157bbHPO++8U/jGN75R6NSpU6FNmzaFQw45pLBs2bLGKAeaheOPP77Qu3fvQsuWLQudO3cu7LvvvoXf//73xePGDHw8n//85wunnXZa8bOxA3UdccQRhe7duxdatmxZ2G677QpHHHFEYcmSJcXjxgxs2H/8x38Uhg4dWqiqqioMGjSocNVVV9U5XltbWzj77LMLXbt2LVRVVRX23XffwpNPPtlE1cInw1133VVIssGx4HkD61u1alXhtNNOK/Tq1avQqlWrwo477lj49re/Xaiuri728byB9f3mN78p7LjjjoWWLVsWunXrVpg0aVJhxYoVxeMNMW7KCoVCoeHzfwAAAAAAaP4a9cWiAAAAAADQnAnRAQAAAACgBCE6AAAAAACUIEQHAAAAAIAShOgAAAAAAFCCEB0AAAAAAEoQogMAAAAAQAlCdAAAAAAAKEGIDgAAAAAAJQjRAQBgE8yaNSsdO3bcLPd68skn061bt7z55pubdJ0+ffrkxz/+ccMU1YjuvPPO7LzzzqmtrW3qUgAA+CcmRAcA4BPv2GOPTVlZWcrKytKiRYt07do1X/jCF3LNNdds1oB1Q+HzEUcckaeeemqz3H/atGk55ZRT0q5du026zvz58/Mv//IvDVTVe84777zsvPPODXrN/fffPy1atMivf/3rBr0uAADUhxAdAIBmYf/998+yZcvy3HPPZfbs2Rk9enROO+20HHTQQVm3bt1GX7dQKGzS+a1bt06XLl02+vyP64UXXsjtt9+eY489dpOv1blz57Rp02bTi9oMjj322Fx66aVNXQYAAP/EhOgAADQLVVVV6datW7bbbruMHDky3/rWt3Lrrbdm9uzZmTVrVpLkueeeS1lZWRYtWlQ8b8WKFSkrK8ucOXOSJHPmzElZWVlmz56dXXfdNVVVVbn//vvzzDPP5OCDD07Xrl3Ttm3bfOpTn8of/vCH4nX23nvvPP/885kyZUpxVnyy4eVcZs6cmb59+6Zly5YZOHBgfvnLX9Y5XlZWlp/97Gc55JBD0qZNm/Tv3z+33Xbbh37/3/72txkxYkS22267Ytv797799tszcODAtGnTJoceemjefvvt/OIXv0ifPn3SqVOnnHrqqampqSme948z6j+qng19x9/97nd1foPp06fnkUceKf427/83WbFiRU444YR07tw57du3zz777JNHHnmkeJ1HHnkko0ePTrt27dK+ffvsuuuuWbBgQfH4+PHjs2DBgjzzzDMf+vsAAEBjEaIDANBs7bPPPhkxYkRuvvnmep971lln5cILL8zjjz+e4cOHZ/Xq1TnggANyzz335OGHH87++++f8ePH54UXXkiS3Hzzzdl+++3z3e9+N8uWLcuyZcs2eN1bbrklp512Wk4//fQsXrw4J510Uo477rjcd999dfpNnz49hx9+eP77v/87BxxwQCZOnJi///3vJev9r//6r+y2227rtb/99tu59NJLc8MNN+TOO+/MnDlzcsghh+SOO+7IHXfckV/+8pe58sorc9NNN33o71Hfej7oiCOOyOmnn54hQ4YUf5sjjjgiSXLYYYdl+fLlmT17dhYuXJiRI0dm3333LV574sSJ2X777TN//vwsXLgwZ511Vlq0aFG8dq9evdK1a9f813/918eqBQAAGpoQHQCAZm3QoEF57rnn6n3ed7/73XzhC19I3759s/XWW2fEiBE56aSTMnTo0PTv3z/nn39++vbtW5yRvfXWW6eioiLt2rVLt27d0q1btw1e96KLLsqxxx6bb3zjGxkwYECmTp2aL37xi7nooovq9Dv22GNz5JFHpl+/frnggguyevXqPPjggyXrff7559OjR4/12teuXZuZM2dml112yec+97kceuihuf/++/Pzn/88gwcPzkEHHZTRo0evF+L/o/rW80GtW7dO27ZtU1lZWfxtWrdunfvvvz8PPvhgbrzxxuy2227p379/LrroonTs2LEY6r/wwgsZM2ZMBg0alP79++ewww7LiBEj6ly/R48eef755z9WLQAA0NCE6AAANGuFQqG4rEh9/OOs7tWrV+eMM87ITjvtlI4dO6Zt27Z5/PHHizPRP67HH388e+65Z522PffcM48//nidtuHDhxf3t9pqq7Rv3z7Lly8ved133nknrVq1Wq+9TZs26du3b/Fz165d06dPn7Rt27ZO24dde2Pq+TgeeeSRrF69Ottss03atm1b3J599tni8ixTp07NCSeckDFjxuTCCy/c4LItrVu3zttvv71JtQAAwMaqbOoCAABgUzz++OPZYYcdkiTl5e/NESkUCsXja9eu3eB5W221VZ3PZ5xxRu6+++5cdNFF6devX1q3bp1DDz00a9asaZS6P7hkSfLeuuS1tbUl+2+77bZ54403PtZ16nvtj6qnvLy8zm+alP5dP2j16tXp3r17cT36D3p/jfXzzjsvRx11VP7zP/8zs2fPzrnnnpsbbrghhxxySLHv3//+93Tu3Pkj7wcAAI3BTHQAAJqte++9N48++mi+9KUvJUkxaP3geuUffMnoh/nTn/6UY489NoccckiGDRuWbt26rbdMTMuWLeu8oHNDdtppp/zpT39a79qDBw/+WHWUsssuu+Svf/3rJl1jY3Xu3Dlvvvlm3nrrrWLbP/6uG/ptRo4cmZdffjmVlZXp169fnW3bbbct9hswYECmTJmS3//+9/niF7+Ya6+9tnjs3XffzTPPPJNddtmlcb4cAAB8BCE6AADNQnV1dV5++eW8+OKLeeihh3LBBRfk4IMPzkEHHZSjjz46yXvLfnzmM58pvjB07ty5+c53vvOxrt+/f//cfPPNWbRoUR555JEcddRR683e7tOnT/74xz/mxRdfzGuvvbbB65x55pmZNWtWZs6cmaeffjqXXHJJbr755pxxxhmb9P3Hjh2befPmfWSI3xh23333tGnTJt/61rfyzDPP5LrrrsusWbPq9OnTp0+effbZLFq0KK+99lqqq6szZsyYjBo1KhMmTMjvf//7PPfcc/nzn/+cb3/721mwYEHeeeednHzyyZkzZ06ef/75/OlPf8r8+fOz0047Fa/7wAMPpKqqKqNGjdrM3xoAAN4jRAcAoFm4884707179/Tp0yf7779/7rvvvlx66aW59dZbU1FRUex3zTXXZN26ddl1110zefLkfO973/tY17/kkkvSqVOn7LHHHhk/fnzGjh2bkSNH1unz3e9+N88991z69u1bcnmRCRMm5Cc/+UkuuuiiDBkyJFdeeWWuvfba7L333hv93ZNk3LhxqayszB/+8IdNus7G2HrrrfOrX/0qd9xxR4YNG5brr78+5513Xp0+X/rSl7L//vtn9OjR6dy5c66//vqUlZXljjvuyOc+97kcd9xxGTBgQL785S/n+eefT9euXVNRUZHXX389Rx99dAYMGJDDDz8848aNy/Tp04vXvf766zNx4sS0adNmM39rAAB4T1nhHxc3BAAAPpEuv/zy3HbbbbnrrruaupTN4rXXXsvAgQOzYMGC4rr3AACwuXmxKAAANBMnnXRSVqxYkTfffDPt2rVr6nIa3XPPPZf/+3//rwAdAIAmZSY6AAAAAACUYE10AAAAAAAoQYgOAAAAAAAlCNEBAAAAAKAEIToAAAAAAJQgRAcAAAAAgBKE6AAAAAAAUIIQHQAAAAAAShCiAwAAAABACUJ0AAAAAAAo4f8Hov0hn553noYAAAAASUVORK5CYII=",
|
|
"text/plain": [
|
|
"<Figure size 1500x300 with 1 Axes>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"colors = [\n",
|
|
" color.convert(\"srgb\")\n",
|
|
" for color in Color.steps(['lch(75% 50 0)', 'lch(75% 50 300)'], steps=len(destinations), space='lch', hue='longer')\n",
|
|
"]\n",
|
|
"\n",
|
|
"colorful_destinations = list(zip(destinations, colors))\n",
|
|
"\n",
|
|
"for label, start_loc in all_starts.items():\n",
|
|
" fig, axes = plt.subplots(1, 1, figsize=(15, 3))\n",
|
|
" \n",
|
|
" for durations, dest in zip(all_trips[label], colorful_destinations):\n",
|
|
" axes.hist(durations, bins=20, alpha=0.7, color=dest[1].to_string(hex=True))\n",
|
|
" axes.axvline(np.mean(durations), color=dest[1].to_string(hex=True), linestyle='--', \n",
|
|
" label=f'{dest[0]} mean: {np.mean(durations):.0f}m')\n",
|
|
" \n",
|
|
" axes.set_title(label)\n",
|
|
" axes.set_xlabel('Duration (minutes)')\n",
|
|
" axes.legend()\n",
|
|
"\n",
|
|
" axes.set_xlim(20, 90)\n",
|
|
" \n",
|
|
" plt.tight_layout()\n",
|
|
" plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "7bf48fc2-1cae-448a-ac20-fc7afaa82c9b",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Fresh Python3.12",
|
|
"language": "python",
|
|
"name": "fresh-python312"
|
|
},
|
|
"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.12.0"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|