Skip to content

Design: Place Search & Geocoding for Location Entry

Date: 2025-11-20 Author: Jake Celentano (via Claude Code) Status: Proposal Project: trip-planner


Problem Statement

Currently, locations in the trip-planner database have no coordinates. When examining the Italy 2026 trip: - Locations exist as placeholders ("LOC1", "test") - latitude and longitude fields are NULL - address field is empty - Map displays nothing because there are no valid coordinates

Root cause: No UX for entering location data. Users would need to manually type latitude/longitude coordinates, which is: - Unusable (who knows the exact coordinates of the Duomo in Florence?) - Error-prone (wrong decimal places, lat/lng swapped) - Time-consuming (looking up coordinates separately)

Goals

  • [ ] Users can search for places by name (e.g., "Duomo Florence")
  • [ ] Autocomplete suggestions appear as user types
  • [ ] Selecting a place auto-fills: name, address, latitude, longitude
  • [ ] Locations appear on map immediately after adding
  • [ ] Works for cities, landmarks, restaurants, hotels, addresses worldwide
  • [ ] Handles location types (accommodation, restaurant, attraction, transport)

Non-Goals

  • Drawing custom routes on map (future - use Mapbox Directions API)
  • Importing locations from Google Maps/TripAdvisor (future)
  • Offline place search (requires internet for geocoding)
  • Multi-language place names (Mapbox handles this automatically)
  • Custom POI database (rely on Mapbox data)

Proposed Solution

High-Level Architecture

┌─────────────────────────────────────────┐
│ Trip Detail Page                        │
│  ├─ Itinerary Tabs (Day 1, Day 2...)   │
│  └─ TripMap (existing)                  │
└───────────────┬─────────────────────────┘
                v
┌─────────────────────────────────────────┐
│ AddLocationDialog (NEW)                 │
│  ├─ PlaceSearchInput (NEW)              │
│  │   └─ Mapbox Geocoding API            │
│  ├─ Location Type Selector              │
│  ├─ Visit Time Input                    │
│  └─ Notes Field                         │
└───────────────┬─────────────────────────┘
                v
        POST /api/locations
                v
      Insert to Supabase
                v
     Update Map + Itinerary UI

Core Components

1. PlaceSearchInput (components/map/PlaceSearchInput.tsx)

Responsibility: Autocomplete search for places using Mapbox Geocoding API

Tech: Client Component with debounced search

Key features: - Text input with autocomplete dropdown - Debounced API calls (300ms delay to avoid excessive requests) - Display suggestions with formatted address - Click suggestion → populate parent form - Loading state while searching - Error handling for API failures

API Used: Mapbox Geocoding API

// Example API call
GET https://api.mapbox.com/geocoding/v5/mapbox.places/duomo%20florence.json
  ?access_token=<TOKEN>
  &types=poi,address
  &limit=5

Response structure:

{
  "features": [
    {
      "place_name": "Cathedral of Santa Maria del Fiore, Florence, Italy",
      "center": [11.2560, 43.7731],  // [lng, lat]
      "properties": {
        "category": "landmark, place of worship"
      }
    }
  ]
}

2. AddLocationDialog (components/trips/AddLocationDialog.tsx)

Responsibility: Form to add location to specific itinerary day

Tech: Client Component with form state

Dependencies: PlaceSearchInput, location type selector

Form fields: - Place search (uses PlaceSearchInput) - Location type: dropdown (accommodation/restaurant/attraction/transport/other) - Visit time: time picker (optional) - Duration: number input in minutes (optional) - Notes: textarea (optional)

Validation: - Name required - Latitude/longitude required (auto-filled from search) - Location type required - Visit time optional but recommended

Submit flow: 1. User searches for place → lat/lng auto-filled 2. User selects location type 3. User optionally sets visit time 4. Click "Add Location" → POST to /api/locations 5. On success → close dialog, refresh itinerary 6. Location appears on map immediately

3. API Route: Create Location (/app/api/locations/route.ts)

Responsibility: Create location in database

Tech: Next.js API route with Supabase

Endpoint:

POST /api/locations
{
  itinerary_id: string    // UUID
  name: string            // "Cathedral of Santa Maria del Fiore"
  address: string         // "Piazza del Duomo, 50122 Florence, Italy"
  latitude: number        // 43.7731
  longitude: number       // 11.2560
  location_type: string   // "attraction"
  visit_time?: string     // "09:00" (HH:MM format)
  duration_minutes?: number
  notes?: string
}

Response:
{
  id: string,
  ...location data
}

Validation: - Verify itinerary exists and belongs to current user (RLS check) - Ensure latitude is between -90 and 90 - Ensure longitude is between -180 and 180 - Validate location_type is one of enum values - Auto-increment sort_order within itinerary

4. Enhanced Trip Detail Page (/app/trips/[id]/page.tsx)

Additions: - "Add Location" button for each day's itinerary - Opens AddLocationDialog with pre-selected itinerary_id - After location added → refetch itinerary data - TripMap automatically updates (already wired to itineraries prop)

Data Flow

User types "Duomo"
PlaceSearchInput debounces (300ms)
Call Mapbox Geocoding API
Display suggestions dropdown
User selects "Cathedral of Santa Maria del Fiore"
Form auto-fills:
  - name: "Cathedral of Santa Maria del Fiore"
  - address: "Piazza del Duomo, 50122 Florence, Italy"
  - latitude: 43.7731
  - longitude: 11.2560
User selects type: "attraction"
User sets visit time: "09:00"
User clicks "Add Location"
POST /api/locations
Insert into trip_planner.locations table
Return success + location data
Dialog closes, itinerary refetches
Map displays new marker at coordinates

Mapbox Geocoding Integration

API Details:

  • Endpoint: https://api.mapbox.com/geocoding/v5/mapbox.places/{query}.json
  • Authentication: Access token in query params or header
  • Rate limits: 100,000 requests/month (free tier)
  • Pricing: $0.50 per 1,000 requests after free tier

Request parameters:

query:        Search text (URL-encoded)
access_token: NEXT_PUBLIC_MAPBOX_TOKEN
types:        poi,address (points of interest + addresses)
limit:        5 (max suggestions)
language:     en (can support multiple languages later)
proximity:    lng,lat (optional - bias results near user location)

Response parsing:

interface GeocodingFeature {
  id: string
  place_name: string              // Full formatted address
  center: [number, number]        // [lng, lat]
  place_type: string[]            // ["poi", "address"]
  properties: {
    category?: string             // "restaurant, italian"
  }
  context?: Array<{               // Hierarchical context
    id: string
    text: string                  // "Florence", "Italy"
  }>
}

Best practices: - Cache recent searches (optional optimization) - Debounce to avoid excessive API calls - Show "No results" state gracefully - Handle network errors with retry logic - Show user-friendly error messages

Alternatives Considered

Option 1: Google Places API

Pros: - More comprehensive POI data - Better restaurant/business info - Reviews/ratings available

Cons: - Requires separate API key/billing - More complex integration - Already using Mapbox for map rendering - Overkill for basic geocoding

Verdict: Rejected - stick with Mapbox for consistency

Option 2: Manual Lat/Lng Entry

Pros: - No API dependency - Simple implementation - No rate limits

Cons: - Unusable UX - Error-prone - Requires external lookups - Defeats purpose of intuitive trip planner

Verdict: Rejected - this is the problem we're solving

Option 3: OpenStreetMap Nominatim

Pros: - Free and open source - No API key required - Good coverage

Cons: - Lower quality results than Mapbox - Slower response times - Rate limited (1 req/sec) - Already paying for Mapbox

Verdict: Rejected - Mapbox is better quality and we're already using it

Option 4: Mapbox Geocoding API ← Chosen

Pros: - Already using Mapbox for map rendering - Single API key/billing - Excellent global coverage - Fast autocomplete responses - Clean API design - 100k free requests/month

Cons: - Requires API key - Costs after free tier - Internet dependency

Verdict: Accepted - best integration with existing stack

Implementation Plan

Phase 1: PlaceSearchInput Component (1.5h)

  • [ ] Create components/map/PlaceSearchInput.tsx (45 min)
  • Text input with controlled state
  • Debounced onChange handler
  • Fetch from Mapbox Geocoding API
  • Display suggestions dropdown
  • [ ] Style suggestions dropdown (20 min)
  • Tailwind CSS styling
  • Loading spinner
  • Hover states
  • [ ] Handle selection callback (15 min)
  • Emit selected place to parent
  • Clear suggestions on select
  • [ ] Error handling (10 min)
  • Network errors
  • No results state
  • Invalid API key

Phase 2: AddLocationDialog Component (1h)

  • [ ] Create components/trips/AddLocationDialog.tsx (30 min)
  • Modal/dialog with form
  • Integrate PlaceSearchInput
  • Location type dropdown
  • Visit time picker
  • Notes textarea
  • [ ] Form state management (15 min)
  • Track all fields
  • Auto-fill from place search
  • Validation
  • [ ] Submit handler (15 min)
  • Call POST /api/locations
  • Handle success/error
  • Close dialog on success

Phase 3: Location API Route (45 min)

  • [ ] Create /app/api/locations/route.ts (30 min)
  • POST handler
  • Validate request body
  • Insert into Supabase
  • Return created location
  • [ ] Test with curl/Postman (15 min)
  • Valid requests
  • Missing required fields
  • Invalid coordinates

Phase 4: Integration with Trip Detail Page (1h)

  • [ ] Update /app/trips/[id]/page.tsx (20 min)
  • Add "Add Location" button per itinerary day
  • Wire up AddLocationDialog
  • Pass itinerary_id to dialog
  • [ ] Refetch logic after location added (15 min)
  • Refresh itinerary data
  • Verify map updates automatically
  • [ ] Loading/error states (15 min)
  • Show loading during API call
  • Display error messages
  • [ ] Polish UI (10 min)
  • Button placement
  • Dialog styling
  • Responsive layout

Phase 5: Testing & Refinement (45 min)

  • [ ] Manual testing workflow (20 min)
  • Search for various places (cities, landmarks, restaurants)
  • Verify coordinates are correct
  • Check map displays markers
  • Test edge cases (no results, network errors)
  • [ ] Fix bugs found during testing (20 min)
  • [ ] Documentation (5 min)
  • Add usage notes to README
  • Document Mapbox API key setup

Total estimate: ~5 hours

Risks & Mitigations

Risk Impact Probability Mitigation
Mapbox API key not set High Medium Check env var, show clear error message
Rate limit exceeded Medium Low Monitor usage, add caching if needed
Poor search results Medium Medium Allow manual coordinate override
Network timeout Medium Low Add 5s timeout, show retry button
Coordinates saved incorrectly High Low Validate lat/lng ranges before insert
RLS blocks location insert High Medium Verify itinerary belongs to user via join

Testing Strategy

Manual testing (current standard):

Happy path: 1. Navigate to trip detail page 2. Click "Add Location" for Day 1 3. Search "Duomo Florence" 4. Select "Cathedral of Santa Maria del Fiore" 5. Verify form auto-fills coordinates 6. Select location type "attraction" 7. Set visit time "09:00" 8. Click submit 9. Verify location appears in day's itinerary 10. Verify marker appears on map at correct position

Edge cases: - Search for nonexistent place → verify "No results" message - Submit without selecting place → verify validation error - Network error during search → verify error message - Invalid Mapbox token → verify clear error to user - Search international locations (Tokyo, Paris) → verify works globally

Database verification:

SELECT name, latitude, longitude, address
FROM trip_planner.locations
WHERE itinerary_id = '<test-itinerary-id>';

Future (when testing added): - Unit tests for PlaceSearchInput debouncing - Integration tests for API route - Component tests for AddLocationDialog

Observability

Not critical for MVP - standard logging sufficient.

Monitor: - Console errors for failed Mapbox API calls - Check Supabase logs for INSERT errors - Monitor Mapbox API usage dashboard (avoid surprise bills)

Future metrics: - Average searches per location added - Most common search queries - Geocoding API response times

Security Considerations

Mapbox API Key: - Use NEXT_PUBLIC_MAPBOX_TOKEN (safe to expose - domain-restricted) - Configure URL restrictions in Mapbox dashboard to limit to: - http://10.89.97.163:3000 (dev) - http://travel.internal (prod) - Domain whitelist prevents abuse

Input validation: - Sanitize search queries before sending to Mapbox - Validate coordinates on backend (not just frontend) - Ensure latitude ∈ [-90, 90], longitude ∈ [-180, 180] - RLS ensures users can only add locations to their own trips

Rate limiting: - No additional rate limiting needed (Mapbox handles this) - Monitor usage to avoid bill shock

Rollout Plan

  1. Development (5 hours)
  2. Implement all phases in /root/projects/trip-planner
  3. Test with Italy 2026 trip

  4. Testing (30 min)

  5. Add real locations to Italy trip
  6. Verify map displays correctly
  7. Test various place types and locations

  8. Commit (5 min)

  9. Git commit with message: feat: add place search and geocoding for location entry
  10. Push to GitHub

  11. Documentation (10 min)

  12. Update README with Mapbox token setup
  13. Add screenshots of place search in action

  14. Production deployment (future)

  15. Update ConfigMap with prod Mapbox token
  16. Deploy to k8s when ready

Success Metrics

Immediate (MVP): - [ ] Can search for "Duomo Florence" and get relevant results - [ ] Selecting a place fills in name, address, lat, lng - [ ] Added location appears on map with correct marker position - [ ] Italy 2026 trip shows real locations instead of empty map - [ ] Can add 10+ locations without issues

Long-term: - Majority of locations added via search (not manual coordinates) - <1% invalid coordinates in database - <5 seconds average time to add a location - No user reports of "can't find my place"

Open Questions

  • [x] Use Mapbox or Google Places API? → Mapbox (already integrated)
  • [ ] Allow manual coordinate override? → YES (add optional lat/lng fields for edge cases)
  • [ ] Support reverse geocoding (pick from map)? → Future enhancement
  • [ ] Cache search results? → No for MVP, add if performance issues
  • [ ] Filter by place type during search? → No, show all results
  • [ ] Proximity bias (search near previous locations)? → Future enhancement
  • [ ] Multi-language support? → Future (Mapbox supports it, just pass language param)

Future Enhancements

Phase 2 features (not in this PR):

  1. Reverse Geocoding - Click map to add location
  2. User clicks map → get coordinates
  3. Call Mapbox reverse geocoding → get place name
  4. Pre-fill form with place data

  5. Route Visualization - Show actual roads between locations

  6. Use Mapbox Directions API
  7. Display estimated travel time/distance
  8. Support walking, driving, transit modes

  9. Place Details - Show photos, ratings, hours

  10. Integrate with additional APIs
  11. Show preview in autocomplete
  12. Help users choose best option

  13. Offline Support - Cache geocoding results

  14. Store recent searches in IndexedDB
  15. Allow offline editing of existing locations
  16. Sync when back online

  17. Bulk Import - Import from CSV, Google Maps, TripAdvisor

  18. Upload trip itinerary CSV
  19. Parse and geocode all locations
  20. Review before importing

  21. Smart Suggestions - AI-powered location recommendations

  22. "Also add nearby restaurants"
  23. "Optimize visit order by proximity"
  24. "Top attractions you might have missed"

Status: Ready for implementation Next Step: Phase 1 - PlaceSearchInput Component Estimated completion: 2025-11-20 (1 day dev time)