Skip to content

Commit 97ef390

Browse files
committed
feat: add TransitAware project and update metadata
- Integrate TransitAware project component and icon - Update site description in metadata.json - Add external link to geoDeltaAudit CRAN package
1 parent 80c9fde commit 97ef390

40 files changed

Lines changed: 11772 additions & 7 deletions

App.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { useState } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
3-
import { ChevronDown, Database, Layout, Sparkles, Map as MapIcon, BookOpen, Gift } from 'lucide-react';
3+
import { ChevronDown, Database, Layout, Sparkles, Map as MapIcon, BookOpen, Gift, Compass } from 'lucide-react';
44
import ShellGame from './src/projects/ShellGame';
5+
import TransitAware from './src/projects/TransitAware';
56
import TransitMap from './src/projects/TransitMap';
67
import IrisCaseStudy from './src/projects/IrisCaseStudy';
78
import FreeStuff from './src/projects/FreeStuff';
@@ -10,11 +11,18 @@ import FreeStuff from './src/projects/FreeStuff';
1011
const PROJECTS = [
1112
{
1213
id: 'shellgame',
13-
name: 'Ecosystem Navigator',
14+
name: 'Ecoverse',
1415
description: 'R Packages & Audit Protocol',
1516
icon: Database,
1617
status: 'active'
1718
},
19+
{
20+
id: 'transitaware',
21+
name: 'TransitAware',
22+
description: 'Layered Transit Analysis',
23+
icon: Compass,
24+
status: 'active'
25+
},
1826
{
1927
id: 'transit-map',
2028
name: 'Mapping Food Vulnerability',
@@ -141,6 +149,18 @@ const App: React.FC = () => {
141149
</motion.div>
142150
)}
143151

152+
{activeProject === 'transitaware' && (
153+
<motion.div
154+
key="transitaware"
155+
initial={{ opacity: 0 }}
156+
animate={{ opacity: 1 }}
157+
exit={{ opacity: 0 }}
158+
transition={{ duration: 0.5 }}
159+
>
160+
<TransitAware />
161+
</motion.div>
162+
)}
163+
144164
{activeProject === 'transit-map' && (
145165
<motion.div
146166
key="transit-map"

metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"name": "Data Acorns",
3-
"description": "A landing page for shellgame and geoDeltaAudit R packages, documenting and quantifying perturbation in population data workflows.",
3+
"description": "The visual story of a translational research analyst making, learning, and doing, featuring R packages, food vulnerability models, and interactive transit spatial networks.",
44
"requestFramePermissions": []
55
}
-129 KB
Loading

public/Shellgame_methods.pdf

-64.6 KB
Binary file not shown.
Binary file not shown.

src/projects/ANOVA.png

-16.9 KB
Loading

src/projects/ComputeRouteMatrix.py

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import os
2+
import json
3+
from datetime import datetime, time, timedelta
4+
from zoneinfo import ZoneInfo
5+
6+
import pandas as pd
7+
import requests
8+
from dotenv import load_dotenv
9+
10+
from Homeicordinates import ORIGIN
11+
12+
13+
MATRIX_API_URL = "https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix"
14+
ROUTES_API_URL = "https://routes.googleapis.com/directions/v2:computeRoutes"
15+
CSV_PATH = "madison_desintation.csv"
16+
POLYLINES_PATH = "route_polylines.json"
17+
18+
# Use the local timezone for the question you are asking, then send it as an
19+
# RFC 3339 timestamp. Google accepts this for the top-level departureTime.
20+
LOCAL_TIMEZONE = ZoneInfo("America/Chicago")
21+
DEPARTURE_WEEKDAY = 2 # Monday=0, Tuesday=1, Wednesday=2
22+
OUTBOUND_DEPARTURE_TIME = time(17, 0) # 5:00 PM
23+
RETURN_DEPARTURE_TIME = time(20, 0) # 8:00 PM
24+
25+
26+
def next_weekday_departure(
27+
weekday: int = DEPARTURE_WEEKDAY,
28+
departure_time: time = OUTBOUND_DEPARTURE_TIME,
29+
) -> str:
30+
"""Return the next requested weekday/time as an RFC 3339 timestamp."""
31+
now = datetime.now(LOCAL_TIMEZONE)
32+
days_ahead = (weekday - now.weekday()) % 7
33+
departure = datetime.combine(
34+
now.date() + timedelta(days=days_ahead),
35+
departure_time,
36+
LOCAL_TIMEZONE,
37+
)
38+
39+
if departure <= now:
40+
departure += timedelta(days=7)
41+
42+
return departure.isoformat()
43+
44+
45+
def lat_lng_waypoint(lat: float, lng: float) -> dict:
46+
return {
47+
"waypoint": {
48+
"location": {
49+
"latLng": {
50+
"latitude": float(lat),
51+
"longitude": float(lng),
52+
}
53+
}
54+
}
55+
}
56+
57+
58+
def route_waypoint(lat: float, lng: float) -> dict:
59+
return {
60+
"location": {
61+
"latLng": {
62+
"latitude": float(lat),
63+
"longitude": float(lng),
64+
}
65+
}
66+
}
67+
68+
69+
def restaurant_waypoints(destinations: pd.DataFrame) -> list[dict]:
70+
return [
71+
lat_lng_waypoint(row["lat"], row["lng"])
72+
for _, row in destinations.iterrows()
73+
]
74+
75+
76+
def get_matrix_payload(
77+
origins: list[dict],
78+
destinations: list[dict],
79+
departure_time: str,
80+
) -> dict:
81+
return {
82+
"origins": origins,
83+
"destinations": destinations,
84+
"travelMode": "TRANSIT",
85+
"transitPreferences": {
86+
# Other useful option for your argument: "FEWER_TRANSFERS".
87+
"routingPreference": "LESS_WALKING",
88+
},
89+
"departureTime": departure_time,
90+
}
91+
92+
93+
def fetch_route_matrix(payload: dict, headers: dict) -> list[dict]:
94+
response = requests.post(MATRIX_API_URL, json=payload, headers=headers, timeout=30)
95+
response.raise_for_status()
96+
return response.json()
97+
98+
99+
def get_route_payload(origin: dict, destination: dict, departure_time: str) -> dict:
100+
return {
101+
"origin": origin,
102+
"destination": destination,
103+
"travelMode": "TRANSIT",
104+
"transitPreferences": {
105+
"routingPreference": "LESS_WALKING",
106+
},
107+
"polylineQuality": "HIGH_QUALITY",
108+
"departureTime": departure_time,
109+
}
110+
111+
112+
def fetch_route_polyline(payload: dict, headers: dict) -> dict:
113+
response = requests.post(ROUTES_API_URL, json=payload, headers=headers, timeout=30)
114+
response.raise_for_status()
115+
data = response.json()
116+
routes = data.get("routes", [])
117+
if not routes:
118+
return {
119+
"duration": None,
120+
"distanceMeters": None,
121+
"encodedPolyline": None,
122+
"status": "NO_ROUTE_RETURNED",
123+
}
124+
125+
route = routes[0]
126+
return {
127+
"duration": route.get("duration"),
128+
"distanceMeters": route.get("distanceMeters"),
129+
"encodedPolyline": route.get("polyline", {}).get("encodedPolyline"),
130+
"status": "OK",
131+
}
132+
133+
134+
def write_route_polylines(
135+
destinations: pd.DataFrame,
136+
api_key: str,
137+
outbound_departure: str,
138+
return_departure: str,
139+
) -> None:
140+
home = route_waypoint(ORIGIN["lat"], ORIGIN["lng"])
141+
headers = {
142+
"Content-Type": "application/json",
143+
"X-Goog-Api-Key": api_key,
144+
"X-Goog-FieldMask": "routes.duration,routes.distanceMeters,routes.polyline.encodedPolyline",
145+
}
146+
147+
route_results = []
148+
for index, row in destinations.iterrows():
149+
restaurant = route_waypoint(row["lat"], row["lng"])
150+
outbound_payload = get_route_payload(
151+
origin=home,
152+
destination=restaurant,
153+
departure_time=outbound_departure,
154+
)
155+
return_payload = get_route_payload(
156+
origin=restaurant,
157+
destination=home,
158+
departure_time=return_departure,
159+
)
160+
161+
print(f"Fetching route geometry for {row['Name']}")
162+
route_results.append(
163+
{
164+
"index": int(index),
165+
"name": row["Name"],
166+
"outbound": fetch_route_polyline(outbound_payload, headers),
167+
"return": fetch_route_polyline(return_payload, headers),
168+
}
169+
)
170+
171+
with open(POLYLINES_PATH, "w", encoding="utf-8") as output_file:
172+
json.dump(
173+
{
174+
"outboundDepartureTime": outbound_departure,
175+
"returnDepartureTime": return_departure,
176+
"routes": route_results,
177+
},
178+
output_file,
179+
indent=2,
180+
)
181+
182+
183+
def response_status_text(status: object) -> str | None:
184+
if status is None:
185+
return None
186+
if isinstance(status, dict):
187+
return status.get("code") or status.get("message") or json.dumps(status)
188+
return str(status)
189+
190+
191+
def apply_matrix_results(
192+
output: pd.DataFrame,
193+
results: list[dict],
194+
prefix: str,
195+
restaurant_index_key: str,
196+
) -> None:
197+
output[f"{prefix}_duration"] = None
198+
output[f"{prefix}_distance_meters"] = None
199+
output[f"{prefix}_condition"] = None
200+
output[f"{prefix}_status"] = None
201+
202+
for element in results:
203+
restaurant_index = element.get(restaurant_index_key)
204+
if restaurant_index is None:
205+
continue
206+
207+
output.at[restaurant_index, f"{prefix}_duration"] = element.get("duration")
208+
output.at[restaurant_index, f"{prefix}_distance_meters"] = element.get(
209+
"distanceMeters"
210+
)
211+
output.at[restaurant_index, f"{prefix}_condition"] = element.get("condition")
212+
output.at[restaurant_index, f"{prefix}_status"] = response_status_text(
213+
element.get("status")
214+
)
215+
216+
217+
def main() -> None:
218+
load_dotenv()
219+
load_dotenv(".env.txt")
220+
221+
api_key = os.getenv("MAPS_API_KEY")
222+
if not api_key:
223+
raise RuntimeError(
224+
"Set MAPS_API_KEY in your environment, .env, or .env.txt before running "
225+
"this script."
226+
)
227+
228+
destinations = pd.read_csv(CSV_PATH)
229+
home_waypoint = lat_lng_waypoint(ORIGIN["lat"], ORIGIN["lng"])
230+
restaurant_locations = restaurant_waypoints(destinations)
231+
outbound_departure = next_weekday_departure(
232+
departure_time=OUTBOUND_DEPARTURE_TIME
233+
)
234+
return_departure = next_weekday_departure(departure_time=RETURN_DEPARTURE_TIME)
235+
236+
outbound_payload = get_matrix_payload(
237+
origins=[home_waypoint],
238+
destinations=restaurant_locations,
239+
departure_time=outbound_departure,
240+
)
241+
return_payload = get_matrix_payload(
242+
origins=restaurant_locations,
243+
destinations=[home_waypoint],
244+
departure_time=return_departure,
245+
)
246+
247+
headers = {
248+
"Content-Type": "application/json",
249+
"X-Goog-Api-Key": api_key,
250+
"X-Goog-FieldMask": "originIndex,destinationIndex,status,condition,distanceMeters,duration",
251+
}
252+
253+
outbound_results = fetch_route_matrix(outbound_payload, headers)
254+
return_results = fetch_route_matrix(return_payload, headers)
255+
output = destinations.copy()
256+
apply_matrix_results(
257+
output=output,
258+
results=outbound_results,
259+
prefix="outbound",
260+
restaurant_index_key="destinationIndex",
261+
)
262+
apply_matrix_results(
263+
output=output,
264+
results=return_results,
265+
prefix="return",
266+
restaurant_index_key="originIndex",
267+
)
268+
269+
output.to_csv("transit_matrix_results.csv", index=False)
270+
write_route_polylines(
271+
destinations=destinations,
272+
api_key=api_key,
273+
outbound_departure=outbound_departure,
274+
return_departure=return_departure,
275+
)
276+
print(f"Outbound departure time: {outbound_departure}")
277+
print(f"Return departure time: {return_departure}")
278+
print("Saved transit_matrix_results.csv")
279+
print(f"Saved {POLYLINES_PATH}")
280+
281+
282+
if __name__ == "__main__":
283+
main()
-239 KB
Binary file not shown.
-133 KB
Binary file not shown.

src/projects/LDAMANOVA.png

-41.7 KB
Loading

0 commit comments

Comments
 (0)