Inspiration
Trip planning means juggling multiple browser tabs, Maps, Notes, blogs, spreadsheets. You're hoping it all comes together, but it rarely does. We wanted a planner that treats a trip the way people actually think about it: a sequence of stops, grouped by day, on a map you can see all at once. And we wanted to build it on open data, with no API keys, no tracking, and no lock-in.
What it does
Shoal is an interactive trip planner. You search for places in plain language ("coffee fullerton"), save them as stops organized by day, and Shoal drops them as markers on a live map. Shoal computes the fastest driving path between every stop, drawing the line right on the map. Stops can be reordered or moved between days.
How we built it
- Backend: Flask with a REST API. Eight endpoints for creating, listing, updating, deleting, and routing stops.
- Frontend: React + Vite + Leaflet for the interactive map.
- Data: OpenStreetMap (Nominatim) for geocoding, OSRM for routing, OpenStreetMap tiles for the map itself.
- Data model: Stops are flat objects with
dayandorderfields. Making it simple to store, flexible to render.
Zero API keys, zero accounts, zero vendor lock-in.
Challenges we ran into
- Coordinate order. Leaflet wants
[lat, lon], OSRM wantslon,lat, GeoJSON wants[lon, lat]. - Error translation. Our first version leaked upstream status codes to clients. A Nominatim rate-limit became a 429 from us, confusing the frontend. We now translate every upstream failure to a clean, explicit response.
- Clever design, bitten by runtime. We defined our error type as both
@dataclassandException. Looked elegant; broke mysteriously during exception chaining. Reverted to plain__init__.
Accomplishments that we're proud of
- A REST API with clean error semantics. Every failure returns JSON the frontend can display directly to users.
- Zero API keys, zero billing, zero lock-in. Anyone can fork Shoal and have it running in ten minutes.
- A data model where "move a stop between days" is a one-field update, not a schema change.
What we learned
- HTTP methods and status codes matter more than they feel like they do. DELETE for deletes, PATCH for partial updates, 404 vs 502. Getting these right makes everything downstream easier.
- When something looks broken, read the actual response body before theorizing because it's almost always a small detail you missed.
What's next for Shoal
- AI-assisted planning. Describe a trip in plain English ("three days in LA, foodie, no theme parks"); Gemini generates suggestions that flow through our existing geocoding and save pipeline.
- Route optimization. Use OSRM's trip service to find the fastest visiting order automatically.
- Multiple profiles. Walking on day 1, driving on day 2 — OSRM supports it, we just need to expose it.
- Collaboration. Share a link, plan together in real time.
- Export. Calendar files and GPX so your plan lives beyond the map.
Built With
- css
- flask
- html
- javascript
- leaflet.js
- nominatim
- openstreetmap
- osrm
- python
- react
- restapi
- vite
Log in or sign up for Devpost to join the conversation.