Waypoint is a full-stack travel tracking platform that automatically builds a user's travel history from uploaded photos. It extracts GPS coordinates and timestamps from photo metadata, groups related photos into trips using DBSCAN clustering, refines detected trips with GPT, and displays saved travel histories on interactive Google Maps.
Live Demo: https://waypoint-madhu.vercel.app
- Upload multiple travel photos through a drag-and-drop interface
- Extract EXIF timestamps and GPS coordinates directly from photos
- Store uploaded images securely using Vercel Blob
- Detect duplicate uploads using SHA-256 file checksums
- Group photos into proposed trips using DBSCAN
- Run clustering through a Python and scikit-learn pipeline
- Refine ambiguous trip boundaries and titles using GPT
- Review detected and unassigned photos before saving
- Confirm proposed trips into a personal travel history
- Display trip locations and routes using Google Maps
- Authenticate users with Clerk
- Store trips, photos, and upload batches in PostgreSQL
- Support both photo-based and legacy CSV-based trip data
Waypoint processes uploaded photos through the following pipeline:
Photo Upload
↓
EXIF Metadata Extraction
↓
Vercel Blob Storage
↓
Python + scikit-learn DBSCAN
↓
GPT Trip Refinement
↓
Trip Review
↓
PostgreSQL Storage
↓
Google Maps Visualization
Users upload photos directly from the browser. Files are uploaded to Vercel Blob instead of passing through a Next.js server route, avoiding Vercel Function request-body limits.
Waypoint uses exifr to extract:
- Latitude
- Longitude
- Date and time taken
- Camera metadata when available
Photos without sufficient GPS or timestamp metadata are marked as unassigned for review.
Waypoint calculates a SHA-256 checksum for each uploaded file. A database constraint prevents the same user from storing an identical photo multiple times, even when the filename has changed.
Valid photo coordinates and timestamps are sent to a Python Vercel Function. The function uses scikit-learn's DBSCAN implementation with the Haversine distance metric to group nearby photos.
Trip detection considers:
- Geographic distance
- Timestamp differences
- Minimum number of photos
- Maximum allowed time gaps
Detected DBSCAN clusters are sent to an OpenAI model for optional refinement. GPT can:
- Generate readable trip titles
- Identify likely cities or destinations
- Improve ambiguous cluster boundaries
- Create natural-language trip summaries
- Assign confidence scores
If GPT refinement is unavailable, Waypoint preserves the original DBSCAN results.
Before trips are saved, users can review:
- Proposed trips
- Assigned photos
- Unassigned photos
- Trip dates
- Detected locations
- Confidence scores
After confirmation, the proposed trips are converted into saved travel-history records.
Saved trips are displayed using the Google Maps JavaScript API. Waypoint places markers for photo locations and draws routes between valid coordinates.
- Next.js
- React
- TypeScript
- Tailwind CSS
- Google Maps JavaScript API
@react-google-maps/apiexifr- Lucide React
- Next.js Route Handlers
- Python
- scikit-learn
- NumPy
- OpenAI API
- Prisma ORM
- PostgreSQL
- Vercel
- Vercel Blob
- Clerk Authentication
waypoint/
├── api/
│ └── cluster.py
├── ml/
│ └── cluster_trips.py
├── prisma/
│ └── schema.prisma
├── public/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ ├── refine-trips/
│ │ │ ├── trips/
│ │ │ └── uploads/
│ │ ├── review/
│ │ ├── trips/
│ │ └── upload/
│ ├── components/
│ │ └── RouteMapPreview.tsx
│ └── lib/
│ ├── clientPhotoMetadata.ts
│ ├── dbscan.ts
│ └── prisma.ts
├── .env.example
├── package.json
├── requirements.txt
└── vercel.json
Install the following before running Waypoint locally:
- Node.js 20 or later
- npm
- Python 3.12
- PostgreSQL database
- Vercel CLI
Install the Vercel CLI globally:
npm install -g vercelgit clone https://github.com/madhus1218/waypoint.git
cd waypointnpm installCreate and activate a virtual environment:
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txtpython3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtCreate a .env.local file in the project root:
DATABASE_URL="your-postgresql-connection-string"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="your-clerk-publishable-key"
CLERK_SECRET_KEY="your-clerk-secret-key"
BLOB_READ_WRITE_TOKEN="your-vercel-blob-token"
OPENAI_API_KEY="your-openai-api-key"
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY="your-google-maps-api-key"Never commit .env.local or any production secrets to GitHub.
Create a PostgreSQL database and set its connection string as DATABASE_URL.
Generate the Prisma client:
npx prisma generateApply the database schema:
npx prisma db pushFor production projects using migrations:
npx prisma migrate deployCreate a Clerk application and add the following variables:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=Ensure the upload, review, and trips pages are protected for signed-in users.
Create or connect a Vercel Blob store to the project.
Add:
BLOB_READ_WRITE_TOKEN=The upload process uses direct client uploads. The browser first requests a temporary upload token from Waypoint's authenticated Blob authorization route.
Create an OpenAI API key and add:
OPENAI_API_KEY=Waypoint uses the key only on the server. Do not prefix it with NEXT_PUBLIC_.
In Google Cloud Console:
- Create or select a Google Cloud project.
- Enable the Maps JavaScript API.
- Enable billing.
- Create an API key.
- Add website restrictions.
- Restrict the key to the Maps JavaScript API.
Example allowed websites:
http://localhost:3000/*
https://waypoint-madhu.vercel.app/*
https://*.vercel.app/*
Add the key as:
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=Because Waypoint includes a Python Vercel Function, use:
vercel devThis runs both:
- The Next.js application
- The Python
/api/clusterendpoint
For frontend-only development, you may also run:
npm run devHowever, the Python clustering endpoint may not behave exactly like it does under Vercel.
Open:
http://localhost:3000
Run:
npm run buildCheck formatting and linting:
npm run lintWaypoint is designed to deploy on Vercel.
- Push the repository to GitHub.
- Import the repository into Vercel.
- Connect a PostgreSQL database.
- Connect a Vercel Blob store.
- Add all required environment variables.
- Redeploy the project.
vercelDeploy to production:
vercel --prodCreates a new authenticated upload batch.
Authorizes a direct client upload to Vercel Blob.
Validates uploaded metadata, stores photo records, triggers clustering, and creates proposed trips.
Runs the Python and scikit-learn DBSCAN pipeline.
Uses GPT to refine DBSCAN-generated trip clusters.
Returns confirmed trips belonging to the current Clerk user.
Waypoint's primary database entities include:
Represents one photo-upload session and tracks processing state.
Stores:
- Owner
- Filename
- Blob URL
- Blob pathname
- File checksum
- Latitude
- Longitude
- Timestamp
- Metadata status
- Assigned trip
Stores:
- Owner
- Title
- Start date
- End date
- City
- Country
- Notes
- Status
- Confidence score
- Center coordinates
- Associated photos
Supports legacy CSV-generated trip coordinates.
A photo must normally include both:
- Valid GPS coordinates
- A valid timestamp
Photos may remain unassigned when:
- GPS metadata is missing
- Timestamp metadata is missing
- Photos are too geographically separated
- Time gaps exceed clustering limits
- There are not enough nearby photos to create a cluster
- The photo was detected as a duplicate
The review page allows users to inspect these results before saving trips.
Waypoint supports common photo formats including:
JPEG
JPG
PNG
WebP
HEIC
HEIF
Metadata availability depends on the original photo and whether another platform removed its EXIF data.
Photos downloaded from social media or sent through certain messaging platforms may no longer contain GPS or timestamp metadata.
- Users must authenticate before uploading photos.
- Upload batches are associated with the current Clerk user.
- Blob upload paths are validated against the authenticated user.
- Trips are queried by Clerk user ID.
- Exact duplicate files are rejected per user.
- Secret API keys remain on the server.
- The OpenAI key is never exposed to the browser.
- Uploaded photos are not shared publicly by default.
- Trip detection depends on the availability and accuracy of EXIF metadata.
- Screenshots usually do not contain useful GPS metadata.
- Social media platforms may remove photo metadata.
- DBSCAN settings may need adjustment for unusually long or multi-city trips.
- GPT refinement requires an active OpenAI API key and available API quota.
- Google Maps requires billing to be enabled.
- HEIC metadata behavior may differ between browsers and devices.
- Python Vercel Functions may have cold-start latency.
Potential future additions include:
- Manual creation of trips from unassigned photos
- Drag-and-drop photo reassignment
- Editable trip dates and destinations
- Reverse geocoding for automatic city and country detection
- Public trip-sharing pages
- Photo galleries for each trip
- Timeline-based travel visualization
- Background processing for large uploads
- Support for Apple Photos and Google Takeout exports
- Improved multi-city trip detection
- Mobile-first map interactions
- Trip export to JSON, CSV, or GPX
Madhumita “Madhu” Subbiah
Georgia Institute of Technology Computer Science
- GitHub: https://github.com/madhus1218
- Portfolio: https://madhusubbiah.tech
- Live Project: https://waypoint-madhu.vercel.app
This project is available for educational and portfolio purposes. Add a formal open-source license before allowing unrestricted reuse or redistribution.