# Geocoding and Map Issues This guide covers geocoding, map display, and location-related problems in Changemaker Lite V2. ## Overview ### Geocoding System Changemaker Lite V2 uses **multi-provider geocoding** with automatic fallback: 1. **Google Geocoding API** - Most accurate, requires API key 2. **Mapbox Geocoding API** - Good quality, requires API key 3. **Nominatim (OpenStreetMap)** - Free, no key required 4. **ArcGIS Geocoding Service** - Good for North America 5. **Photon (OpenStreetMap)** - Free alternative 6. **HERE Geocoding API** - Paid option ### Geocoding Queue - **BullMQ queue** - Async geocoding for bulk imports - **Rate limiting** - Respects provider rate limits - **Retry logic** - Auto-retry failed geocodes - **Priority** - Manual geocodes prioritized over bulk ### Map Display - **Leaflet.js** - Open-source map library - **OpenStreetMap tiles** - Free map tiles - **Circle markers** - Color-coded by cut assignment - **Polygon overlays** - Cut boundaries - **Geolocate** - Find user's current location --- ## Geocoding Failures ### Address Not Found **Severity:** 🟡 Medium #### Symptoms Location shows `null` latitude/longitude after geocoding attempt. API logs: ``` WARN Geocoding failed for address: "123 Fake St, Nowhere": No results from any provider ``` #### Common Causes 1. **Invalid address** - Address doesn't exist 2. **Typo** - Misspelled street/city/postal code 3. **Incomplete address** - Missing city or postal code 4. **Wrong country** - Address in different country 5. **Rural address** - Not in geocoding databases #### Solutions **Solution 1: Verify address format** ```bash # Good address format (Canadian): 123 Main Street, Toronto, ON M5H 2N2 # Good address format (US): 123 Main Street, New York, NY 10001 # Bad formats: 123 Main # Missing city/postal Main Street # Missing number Toronto # Too vague ``` **Solution 2: Test address manually** ```bash # Test via Nominatim (no API key needed) curl "https://nominatim.openstreetmap.org/search?q=123+Main+Street,Toronto,ON&format=json" # Should return array with results # If empty, address not found ``` **Solution 3: Try alternative formats** ```bash # If "123 Main Street, Toronto ON M5H 2N2" fails, try: # - "123 Main St, Toronto ON M5H2N2" (no space in postal) # - "123 Main Street, Toronto Ontario M5H 2N2" (full province) # - "123 Main Street, M5H 2N2" (postal code only) # - "M5H 2N2" (postal code geocoding) ``` **Solution 4: Check geocoding logs** ```bash # View detailed geocoding attempts docker compose logs api | grep "Geocoding\|geocode" # Shows: # Trying provider: google # Google geocoding failed: Invalid request # Trying provider: nominatim # Nominatim geocoding succeeded ``` **Solution 5: Manually set coordinates** In admin UI (LocationsPage): 1. Find location in table 2. Click Edit 3. Manually enter lat/lng (from Google Maps) 4. Save Or via SQL: ```bash docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "UPDATE \"Location\" SET latitude = 43.65, longitude = -79.38 WHERE address = '123 Main Street';" ``` #### Prevention - **Address validation** - Validate format before saving - **Postal code lookup** - Use postal code if full address fails - **Manual review** - Flag failed geocodes for manual review - **Alternative sources** - Try multiple address formats --- ### All Providers Failed **Severity:** 🟠 High #### Symptoms ``` ERROR Geocoding failed: All providers failed for address: "123 Main St" ``` All 6 geocoding providers returned no results or errors. #### Common Causes 1. **Network issue** - Can't reach external APIs 2. **Rate limits** - All providers rate limited 3. **Invalid API keys** - Google/Mapbox keys invalid 4. **Bad address** - Address truly doesn't exist 5. **Provider outages** - Services down #### Solutions **Solution 1: Check network connectivity** ```bash # Test DNS resolution docker compose exec api ping -c 3 nominatim.openstreetmap.org # Test HTTPS connection docker compose exec api curl -I https://nominatim.openstreetmap.org # If fails, network issue ``` **Solution 2: Test each provider manually** ```bash # Nominatim (free, no key) curl "https://nominatim.openstreetmap.org/search?q=123+Main+Street,Toronto&format=json" # Google (requires GOOGLE_GEOCODING_API_KEY) curl "https://maps.googleapis.com/maps/api/geocode/json?address=123+Main+Street,Toronto&key=YOUR_KEY" # Mapbox (requires MAPBOX_API_KEY) curl "https://api.mapbox.com/geocoding/v5/mapbox.places/123+Main+Street,Toronto.json?access_token=YOUR_KEY" ``` **Solution 3: Check API keys** ```bash # Verify API keys in .env cat .env | grep -E "GOOGLE_GEOCODING_API_KEY|MAPBOX_API_KEY|HERE_API_KEY" # Should show non-empty values # If empty, providers requiring keys won't work ``` **Solution 4: Check rate limits** ```bash # View geocoding stats curl http://localhost:4000/api/map/geocoding/stats \ -H "Authorization: Bearer YOUR_TOKEN" # Shows: # { # "totalAttempts": 1523, # "successful": 1450, # "failed": 73, # "byProvider": { # "google": { "attempts": 500, "successes": 480 }, # "nominatim": { "attempts": 600, "successes": 570 } # } # } ``` **Solution 5: Wait and retry** Rate limits reset after time: - **Nominatim**: 1 request/second (resets immediately) - **Google**: 50 requests/second (resets after 1 second) - **Mapbox**: 600 requests/minute (resets after 1 minute) ```bash # Retry geocoding after wait curl -X POST http://localhost:4000/api/map/locations/LOCATION_ID/geocode \ -H "Authorization: Bearer YOUR_TOKEN" ``` #### Prevention - **API key monitoring** - Alert on API key errors - **Rate limit tracking** - Monitor usage against limits - **Provider rotation** - Distribute load across providers - **Graceful degradation** - Continue with partial results --- ### Low Confidence Results **Severity:** 🟢 Low #### Symptoms Geocoding succeeds but coordinates seem wrong or imprecise. Example: - Address: "123 Main Street, Toronto" - Geocoded to: Center of Toronto (not specific address) #### Common Causes 1. **Ambiguous address** - Multiple matches 2. **Incomplete address** - Missing street number 3. **Rural address** - Only city-level precision 4. **Provider limitation** - Provider doesn't have precise data #### Solutions **Solution 1: Check geocoding confidence** ```bash # View location details curl http://localhost:4000/api/map/locations/LOCATION_ID \ -H "Authorization: Bearer YOUR_TOKEN" # Response includes: # { # "geocodingProvider": "nominatim", # "geocodingConfidence": "low", # or "high", "medium" # "latitude": 43.65, # "longitude": -79.38 # } ``` **Solution 2: Add more detail to address** ```bash # Low confidence: "Main Street, Toronto" # Higher confidence: "123 Main Street, Toronto, ON M5H 2N2" # Best confidence: "123 Main Street, Toronto, Ontario M5H 2N2, Canada" ``` **Solution 3: Use postal code geocoding** For Canadian addresses, postal code is often more accurate: ```bash # Update location with postal code UPDATE "Location" SET "postalCode" = 'M5H 2N2' WHERE id = 'LOCATION_ID'; # Re-geocode (will use postal code) curl -X POST http://localhost:4000/api/map/locations/LOCATION_ID/geocode \ -H "Authorization: Bearer YOUR_TOKEN" ``` **Solution 4: Manually verify on map** In LocationsPage: 1. Click location row 2. View on map 3. If wrong, manually drag marker to correct location 4. Save **Solution 5: Flag for review** ```bash # Mark low-confidence results for manual review docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, address, \"geocodingConfidence\" FROM \"Location\" WHERE \"geocodingConfidence\" = 'low' ORDER BY \"createdAt\" DESC LIMIT 50;" ``` #### Prevention - **Confidence tracking** - Store confidence score - **Manual review queue** - Review low-confidence results - **Address validation** - Validate format before geocoding - **Postal code priority** - Use postal code when available --- ### Rate Limit Exceeded **Severity:** 🟡 Medium #### Symptoms ``` ERROR Geocoding rate limit exceeded for provider: google WARN Retrying with next provider: mapbox ``` Or: ``` ERROR 429 Too Many Requests from https://maps.googleapis.com/ ``` #### Common Causes 1. **Bulk import** - Geocoding thousands of addresses at once 2. **No API key** - Free tier has lower limits 3. **Shared IP** - Multiple users on same IP 4. **Testing** - Repeated manual geocodes #### Solutions **Solution 1: Check rate limits** Per-provider limits: | Provider | Free Tier | With API Key | |----------|-----------|--------------| | Nominatim | 1/sec | N/A | | Google | N/A | 50/sec (or paid limit) | | Mapbox | N/A | 600/min | | ArcGIS | 1000/day | Varies | | Photon | Unlimited | N/A | | HERE | N/A | Varies by plan | **Solution 2: Use geocoding queue** For bulk operations: ```bash # Queue all ungeocoded locations curl -X POST http://localhost:4000/api/map/locations/queue-geocoding \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"batchSize": 100}' # Queue processes at rate-limit-safe speed ``` **Solution 3: Add API keys** ```bash # In .env GOOGLE_GEOCODING_API_KEY=your-key-here MAPBOX_API_KEY=your-key-here # Restart API docker compose restart api ``` **Solution 4: Distribute across providers** ```bash # Check provider usage curl http://localhost:4000/api/map/geocoding/stats \ -H "Authorization: Bearer YOUR_TOKEN" # If one provider is overused, system auto-rotates to others ``` **Solution 5: Wait and retry** ```bash # Wait for rate limit window to reset # Nominatim: 1 second # Google: Check quota reset time # Mapbox: 1 minute # Retry failed geocodes curl -X POST http://localhost:4000/api/map/locations/retry-failed \ -H "Authorization: Bearer YOUR_TOKEN" ``` #### Prevention - **API keys** - Use paid tiers for higher limits - **Queue system** - Respect rate limits automatically - **Provider rotation** - Distribute load - **Monitor usage** - Alert when approaching limits --- ## Map Display Issues ### Map Not Loading **Severity:** 🟠 High #### Symptoms Map container shows blank white/gray box. No tiles loaded. Browser console: ``` Error loading tile: https://tile.openstreetmap.org/... Failed to load resource: net::ERR_BLOCKED_BY_CLIENT ``` #### Common Causes 1. **Ad blocker** - Blocking OSM tile requests 2. **Network issue** - Can't reach tile server 3. **CSP headers** - Content Security Policy blocking 4. **Leaflet CSS missing** - Styles not imported #### Solutions **Solution 1: Disable ad blocker** 1. Disable ad blocker for your site 2. Or whitelist `*.openstreetmap.org` 3. Refresh page **Solution 2: Check network** ```bash # Test tile server curl -I https://tile.openstreetmap.org/0/0/0.png # Should return 200 OK # If fails, network or DNS issue ``` **Solution 3: Verify Leaflet CSS** In map component file: ```typescript // Must import Leaflet CSS import 'leaflet/dist/leaflet.css'; ``` Check in browser DevTools: - Elements tab → Check if `.leaflet-container` has styles - Network tab → Check if `leaflet.css` loaded **Solution 4: Check CSP headers** In `nginx/conf.d/default.conf`: ```nginx # Allow OSM tiles add_header Content-Security-Policy "... img-src 'self' data: https://*.openstreetmap.org;"; ``` **Solution 5: Try alternative tile provider** ```typescript // In map component ``` #### Prevention - **Ad blocker warning** - Detect and show warning - **Fallback tiles** - Multiple tile providers - **Error boundaries** - Catch map loading errors - **Clear documentation** - Document ad blocker issue --- ### Markers Not Appearing **Severity:** 🟡 Medium #### Symptoms Map loads but location markers don't appear. #### Common Causes 1. **No data** - No locations fetched 2. **Null coordinates** - Locations not geocoded 3. **Out of bounds** - Markers outside map view 4. **Rendering error** - React component error #### Solutions **Solution 1: Check data loaded** ```javascript // In browser console console.log('Locations:', locations); // Should show array of locations with lat/lng // If empty or undefined, data not loaded ``` **Solution 2: Verify coordinates** ```bash # Check locations have coordinates docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT COUNT(*) FROM \"Location\" WHERE latitude IS NOT NULL AND longitude IS NOT NULL;" # If 0, no locations geocoded ``` **Solution 3: Zoom to markers** ```typescript // In map component, fit bounds to markers useEffect(() => { if (locations.length > 0 && mapRef.current) { const bounds = locations .filter(l => l.latitude && l.longitude) .map(l => [l.latitude, l.longitude]); if (bounds.length > 0) { mapRef.current.fitBounds(bounds, { padding: [50, 50] }); } } }, [locations]); ``` **Solution 4: Check marker rendering** ```typescript // Verify CircleMarker component {locations.map((location) => { if (!location.latitude || !location.longitude) return null; return ( ); })} ``` **Solution 5: Check browser console** Look for React errors: ``` Warning: Each child in a list should have a unique "key" prop Error: Invalid latitude/longitude ``` #### Prevention - **Data validation** - Ensure data has coordinates - **Error boundaries** - Catch rendering errors - **Loading states** - Show loading while fetching - **Empty states** - Show message if no data --- ### Cuts Not Rendering **Severity:** 🟡 Medium #### Symptoms Cut polygons don't appear on map. #### Common Causes 1. **Invalid GeoJSON** - Malformed polygon data 2. **Wrong coordinate order** - GeoJSON uses [lng, lat], Leaflet uses [lat, lng] 3. **Self-intersecting polygon** - Invalid polygon geometry 4. **Out of bounds** - Polygon outside map view #### Solutions **Solution 1: Validate GeoJSON** ```bash # Check cut geometry docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, name, ST_AsGeoJSON(geometry) FROM \"Cut\" WHERE id = 'CUT_ID';" # Verify format: # { # "type": "Polygon", # "coordinates": [[[lng1, lat1], [lng2, lat2], ...]] # } ``` **Solution 2: Convert coordinates** ```typescript // GeoJSON uses [lng, lat] const geojson = { type: 'Polygon', coordinates: [[[-79.38, 43.65], [-79.37, 43.65], ...]] }; // Convert to Leaflet [lat, lng] const leafletCoords = geojson.coordinates[0].map(([lng, lat]) => [lat, lng]); ``` **Solution 3: Check for self-intersection** ```sql -- Validate polygon geometry SELECT id, name, ST_IsValid(geometry) as is_valid FROM "Cut" WHERE NOT ST_IsValid(geometry); -- If invalid, show reason SELECT id, name, ST_IsValidReason(geometry) FROM "Cut" WHERE NOT ST_IsValid(geometry); -- Fix with buffer(0) UPDATE "Cut" SET geometry = ST_Buffer(geometry, 0) WHERE NOT ST_IsValid(geometry); ``` **Solution 4: Zoom to cut** ```typescript // Fit map to cut bounds useEffect(() => { if (cut?.geometry && mapRef.current) { const coords = cut.geometry.coordinates[0].map(([lng, lat]) => [lat, lng]); const bounds = L.latLngBounds(coords); mapRef.current.fitBounds(bounds, { padding: [50, 50] }); } }, [cut]); ``` **Solution 5: Check Polygon component** ```typescript // Verify Polygon rendering ``` #### Prevention - **Geometry validation** - Validate on save - **Drawing tools** - Use validated drawing library - **Import validation** - Check imported geometries - **Error handling** - Gracefully handle invalid geometries --- ### GPS Not Working **Severity:** 🟡 Medium #### Symptoms Geolocate button doesn't work or shows error. Browser shows permission prompt but location never loads. #### Common Causes 1. **HTTPS required** - Geolocation API requires HTTPS (or localhost) 2. **Permission denied** - User denied location permission 3. **GPS unavailable** - Device has no GPS 4. **Browser doesn't support** - Old browser #### Solutions **Solution 1: Check HTTPS** Geolocation API requires: - **HTTPS** (https://) - OR **localhost** (http://localhost) - OR **127.0.0.1** (http://127.0.0.1) ```bash # In production, ensure HTTPS # Via Pangolin tunnel or Cloudflare ``` **Solution 2: Grant permission** 1. Click lock icon in address bar 2. Location → Allow 3. Refresh page 4. Try geolocate again **Solution 3: Test geolocation API** ```javascript // In browser console navigator.geolocation.getCurrentPosition( (pos) => console.log('Location:', pos.coords), (err) => console.error('Error:', err) ); // Errors: // PERMISSION_DENIED - User denied // POSITION_UNAVAILABLE - GPS unavailable // TIMEOUT - Taking too long ``` **Solution 4: Increase timeout** ```typescript // In geolocate code navigator.geolocation.getCurrentPosition( successCallback, errorCallback, { timeout: 10000, // 10 seconds (default: 5000) enableHighAccuracy: true, maximumAge: 0 } ); ``` **Solution 5: Fallback to IP geolocation** ```typescript // If GPS fails, use IP-based location const fallbackLocation = async () => { const response = await fetch('https://ipapi.co/json/'); const data = await response.json(); return { latitude: data.latitude, longitude: data.longitude }; }; ``` #### Prevention - **HTTPS in production** - Use secure connection - **Permission prompts** - Clear instructions - **Fallback options** - IP geolocation as backup - **Error handling** - User-friendly error messages --- ## Coordinate Issues ### Invalid Lat/Lng **Severity:** 🟡 Medium #### Symptoms ``` Error: Invalid latitude/longitude values ``` Or markers appear in wrong location (ocean, wrong country). #### Common Causes 1. **Swapped coordinates** - Latitude and longitude reversed 2. **Out of range** - Latitude > 90 or Longitude > 180 3. **Wrong sign** - Positive instead of negative (or vice versa) 4. **Decimal precision** - Too many/few decimal places #### Solutions **Solution 1: Validate ranges** Valid ranges: - **Latitude**: -90 to 90 - **Longitude**: -180 to 180 ```bash # Find invalid coordinates docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, address, latitude, longitude FROM \"Location\" WHERE latitude < -90 OR latitude > 90 OR longitude < -180 OR longitude > 180;" ``` **Solution 2: Check coordinate order** ```bash # Common mistake: swapped lat/lng # Toronto should be: # Latitude: 43.65 (positive, North) # Longitude: -79.38 (negative, West) # If showing as 79.38, -43.65, they're swapped # Fix: UPDATE "Location" SET latitude = longitude, longitude = latitude WHERE id = 'LOCATION_ID'; ``` **Solution 3: Verify hemisphere** For North American locations: - **Latitude**: Positive (North) - **Longitude**: Negative (West) ```bash # If US/Canada location has positive longitude, wrong sign UPDATE "Location" SET longitude = longitude * -1 WHERE country = 'Canada' AND longitude > 0; ``` **Solution 4: Check decimal precision** ```bash # Good precision (6 decimals ≈ 0.1m accuracy): Latitude: 43.651234 Longitude: -79.381234 # Too few decimals (imprecise): Latitude: 43.65 Longitude: -79.38 # Too many decimals (unnecessary): Latitude: 43.651234567890 Longitude: -79.381234567890 ``` **Solution 5: Visual verification** 1. Open Google Maps 2. Enter coordinates: `43.651234, -79.381234` 3. Verify location matches address 4. If wrong, get correct coordinates from Google Maps #### Prevention - **Coordinate validation** - Check ranges before save - **Visual preview** - Show on map before save - **Import validation** - Validate imported coordinates - **Decimal precision** - Round to 6 decimals --- ### Out of Bounds Coordinates **Severity:** 🟢 Low #### Symptoms Markers appear outside expected area (different country/continent). #### Solutions **Solution 1: Set map bounds** ```typescript // Limit map to expected region const bounds = L.latLngBounds( [41.0, -95.0], // Southwest corner [50.0, -74.0] // Northeast corner (covers eastern Canada/US) ); ``` **Solution 2: Filter locations by bounds** ```typescript // Only show locations in expected region const filteredLocations = locations.filter(location => { return location.latitude >= 41 && location.latitude <= 50 && location.longitude >= -95 && location.longitude <= -74; }); ``` --- ### Projection Errors (NAR Data) **Severity:** 🟡 Medium #### Symptoms Locations imported from NAR data appear in wrong place. #### Common Causes 1. **Wrong projection** - NAR uses EPSG:3347 (Lambert), not WGS84 2. **Missing conversion** - Coordinates not converted to lat/lng 3. **Coordinate swap** - BG_X and BG_Y reversed #### Solutions **Solution 1: Verify NAR import uses proj4** In `api/src/modules/map/locations/nar-import.service.ts`: ```typescript import proj4 from 'proj4'; // Define EPSG:3347 (NAR projection) proj4.defs('EPSG:3347', '+proj=lcc +lat_0=63.390675 +lon_0=-91.86666666666666 ' + '+lat_1=49 +lat_2=77 +x_0=6200000 +y_0=3000000 ' + '+ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs' ); // Convert const [longitude, latitude] = proj4('EPSG:3347', 'WGS84', [bgX, bgY]); ``` **Solution 2: Check coordinate order** NAR Address files: - **BG_X**: Easting (X coordinate in meters) - **BG_Y**: Northing (Y coordinate in meters) Conversion order: `[BG_X, BG_Y]` → `[longitude, latitude]` **Solution 3: Verify conversion** ```bash # Test conversion manually docker compose exec api node -e " const proj4 = require('proj4'); proj4.defs('EPSG:3347', '+proj=lcc +lat_0=63.390675 +lon_0=-91.86666666666666 +lat_1=49 +lat_2=77 +x_0=6200000 +y_0=3000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); // Example Toronto coordinates in EPSG:3347: const [lng, lat] = proj4('EPSG:3347', 'WGS84', [6458123, 3534567]); console.log('Lat:', lat, 'Lng:', lng); // Should be approximately: Lat: 43.65 Lng: -79.38 " ``` **Solution 4: Re-import NAR data** If imported incorrectly: ```bash # Delete bad data docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "DELETE FROM \"Location\" WHERE \"importSource\" = 'NAR';" # Re-import with correct projection # Via admin UI: /app/map/locations → NAR Import tab ``` #### Prevention - **Projection validation** - Test conversion on sample data - **Visual verification** - Show import preview on map - **Documentation** - Document NAR projection requirements - **Import validation** - Check coordinates are in expected range --- ## Queue Issues ### Geocoding Queue Stuck **Severity:** 🟡 Medium #### Symptoms Locations remain ungeocoded even though queue is running. Queue shows jobs but they never process. #### Solutions **Solution 1: Check queue status** ```bash # View queue stats curl http://localhost:4000/api/map/geocoding/queue/stats \ -H "Authorization: Bearer YOUR_TOKEN" # Shows: # { # "waiting": 150, # "active": 0, # Should be > 0 if processing # "completed": 2500, # "failed": 25 # } ``` **Solution 2: Check worker is running** ```bash # Worker should log processing docker compose logs api | grep -i "geocoding worker\|processing geocode" # Should show: # Geocoding worker started # Processing geocode job for location: abc-123 ``` **Solution 3: Restart queue worker** ```bash # Restart API (restarts worker) docker compose restart api # Check worker started docker compose logs api | grep "Geocoding worker started" ``` **Solution 4: Check Redis connection** ```bash # Test Redis docker compose exec redis redis-cli -a YOUR_REDIS_PASSWORD ping # Should return: PONG # Check queue keys docker compose exec redis redis-cli -a YOUR_REDIS_PASSWORD keys "bull:geocoding:*" ``` **Solution 5: Manually process stuck jobs** ```bash # Retry failed jobs curl -X POST http://localhost:4000/api/map/geocoding/queue/retry-failed \ -H "Authorization: Bearer YOUR_TOKEN" # Clean stuck jobs curl -X POST http://localhost:4000/api/map/geocoding/queue/clean \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{"status": "failed", "grace": 86400000}' # Clean failed jobs older than 1 day ``` #### Prevention - **Health checks** - Monitor worker health - **Dead letter queue** - Move repeatedly failed jobs - **Alerting** - Alert if queue backed up - **Auto-restart** - Restart worker if stuck --- ### Jobs Failing **Severity:** 🟡 Medium #### Symptoms Queue shows high failed job count. Locations remain ungeocoded with error status. #### Solutions **Solution 1: View failed jobs** ```bash # Get failed job details curl http://localhost:4000/api/map/geocoding/queue/failed \ -H "Authorization: Bearer YOUR_TOKEN" # Shows: # [ # { # "id": "123", # "data": { "locationId": "abc", "address": "..." }, # "failedReason": "All providers failed", # "attemptsMade": 3 # } # ] ``` **Solution 2: Check error patterns** ```bash # Common failure reasons docker compose logs api | grep "Geocoding failed" | sort | uniq -c # Example output: # 45 Geocoding failed: Rate limit exceeded # 12 Geocoding failed: No results found # 3 Geocoding failed: Network error ``` **Solution 3: Retry with different settings** ```bash # Retry with longer timeout curl -X POST http://localhost:4000/api/map/locations/LOCATION_ID/geocode \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"timeout": 30000}' # 30 seconds ``` **Solution 4: Manual intervention** For repeatedly failing addresses: 1. Open LocationsPage 2. Find failed locations 3. Review address (fix typos) 4. Manually set coordinates if needed 5. Or delete if invalid #### Prevention - **Retry logic** - Auto-retry with exponential backoff - **Error categorization** - Permanent vs transient failures - **Manual review queue** - Flag for manual review after N attempts - **Address validation** - Validate before geocoding --- ## Performance Issues ### Slow Geocoding **Severity:** 🟡 Medium #### Symptoms Geocoding takes 5-10+ seconds per address. Bulk imports very slow. #### Solutions **Solution 1: Use faster providers first** Provider speed (fastest to slowest): 1. Google (with API key) - ~200ms 2. Mapbox (with API key) - ~300ms 3. Nominatim - ~500ms 4. ArcGIS - ~800ms 5. Photon - ~1000ms 6. HERE - ~400ms Configure in `api/src/modules/map/geocoding/geocoding.service.ts`. **Solution 2: Increase concurrency** In geocoding queue worker: ```typescript // Increase concurrent geocoding const worker = new Worker('geocoding', processor, { concurrency: 5, // Process 5 at a time (default: 1) limiter: { max: 50, // Max 50 jobs per second duration: 1000 } }); ``` **Solution 3: Use bulk geocoding APIs** Some providers offer batch geocoding: ```bash # Google Batch Geocoding (requires Business plan) # Can geocode up to 100 addresses in one request ``` **Solution 4: Cache results** ```typescript // Cache geocoding results in Redis const cacheKey = `geocode:${address}`; const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } const result = await geocode(address); await redis.setex(cacheKey, 86400, JSON.stringify(result)); // Cache 24h return result; ``` **Solution 5: Parallel processing** ```typescript // Geocode multiple addresses in parallel const addresses = ['123 Main St', '456 Oak Ave', ...]; const results = await Promise.all( addresses.map(address => geocode(address)) ); ``` #### Prevention - **Queue system** - Don't block UI on geocoding - **Paid tiers** - Faster with API keys - **Caching** - Cache frequent addresses - **Parallel processing** - Process multiple at once --- ### Too Many API Calls **Severity:** 🟡 Medium #### Symptoms High API usage on Google/Mapbox. Approaching or exceeding quota. #### Solutions **Solution 1: Monitor usage** ```bash # Check geocoding stats curl http://localhost:4000/api/map/geocoding/stats \ -H "Authorization: Bearer YOUR_TOKEN" # Track API costs: # Google: $5 per 1000 requests (after 40k free/month) # Mapbox: $0.50 per 1000 requests (after 100k free/month) ``` **Solution 2: Use free providers first** Reorder provider priority: ```typescript // In geocodingService.ts const providers = [ 'nominatim', // Free (try first) 'photon', // Free 'arcgis', // Free (1000/day) 'google', // Paid (use only if others fail) 'mapbox', // Paid 'here' // Paid ]; ``` **Solution 3: Cache aggressively** ```typescript // Cache geocoding results permanently const cacheKey = `geocode:${normalizeAddress(address)}`; const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } const result = await geocode(address); await redis.set(cacheKey, JSON.stringify(result)); // No expiration return result; ``` **Solution 4: Deduplicate requests** ```bash # Before geocoding, check if address already geocoded docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT latitude, longitude FROM \"Location\" WHERE LOWER(address) = LOWER('123 Main Street, Toronto') AND latitude IS NOT NULL LIMIT 1;" # If exists, copy coordinates instead of geocoding again ``` **Solution 5: Set quota alerts** In Google Cloud Console: 1. Navigate to Geocoding API 2. Set quota alerts (e.g., 80% of limit) 3. Receive email before exceeding quota #### Prevention - **Cache everything** - Never geocode same address twice - **Free providers first** - Use paid only as fallback - **Quota monitoring** - Alert before exceeding - **Cost tracking** - Monitor API costs monthly --- ## Data Quality ### Duplicate Locations **Severity:** 🟢 Low #### Symptoms Same address appears multiple times in locations table. #### Solutions **Solution 1: Find duplicates** ```bash # Find duplicate addresses docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT address, COUNT(*), array_agg(id) FROM \"Location\" GROUP BY LOWER(address) HAVING COUNT(*) > 1;" ``` **Solution 2: Merge duplicates** ```bash # Keep oldest, delete newer # (After reassigning foreign keys to kept record) DELETE FROM "Location" AS l1 WHERE EXISTS ( SELECT 1 FROM "Location" AS l2 WHERE LOWER(l2.address) = LOWER(l1.address) AND l2."createdAt" < l1."createdAt" ); ``` **Solution 3: Add unique constraint** ```prisma model Location { id String @id @default(uuid()) address String @@unique([address]) // Prevent duplicates } ``` #### Prevention - **Unique constraints** - Database prevents duplicates - **Upsert logic** - Update if exists, create if not - **Import validation** - Check for duplicates before import - **Case-insensitive comparison** - Normalize before checking --- ### Ungeocoded Locations **Severity:** 🟡 Medium #### Symptoms Many locations with `null` latitude/longitude. #### Solutions **Solution 1: Count ungeocoded** ```bash docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT COUNT(*) FROM \"Location\" WHERE latitude IS NULL;" ``` **Solution 2: Queue all ungeocoded** ```bash # Via API curl -X POST http://localhost:4000/api/map/locations/queue-geocoding \ -H "Authorization: Bearer YOUR_TOKEN" # Queues all locations with null coordinates ``` **Solution 3: View on Data Quality Dashboard** Navigate to `/app/map/data-quality`: - Shows geocoding rate - Lists ungeocoded locations - Allows bulk geocoding **Solution 4: Export ungeocoded for manual review** ```bash # Export to CSV docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "COPY (SELECT id, address, city, \"postalCode\" FROM \"Location\" WHERE latitude IS NULL) TO STDOUT WITH CSV HEADER" > ungeocoded.csv ``` #### Prevention - **Geocode on create** - Auto-geocode new locations - **Required coordinates** - Don't allow creating without geocoding - **Dashboard monitoring** - Track geocoding rate - **Regular cleanup** - Periodic geocoding of ungeocoded --- ## Useful Commands ### Geocoding Operations ```bash # Geocode single location curl -X POST http://localhost:4000/api/map/locations/LOCATION_ID/geocode \ -H "Authorization: Bearer YOUR_TOKEN" # Bulk geocode via queue curl -X POST http://localhost:4000/api/map/locations/queue-geocoding \ -H "Authorization: Bearer YOUR_TOKEN" # Check geocoding stats curl http://localhost:4000/api/map/geocoding/stats \ -H "Authorization: Bearer YOUR_TOKEN" # Retry failed geocodes curl -X POST http://localhost:4000/api/map/geocoding/queue/retry-failed \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### Database Queries ```bash # Count by geocoding status docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT COUNT(*) FILTER (WHERE latitude IS NOT NULL) as geocoded, COUNT(*) FILTER (WHERE latitude IS NULL) as ungeocoded FROM \"Location\";" # List ungeocoded docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, address FROM \"Location\" WHERE latitude IS NULL LIMIT 50;" # Geocoding provider stats docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT \"geocodingProvider\", COUNT(*) FROM \"Location\" WHERE \"geocodingProvider\" IS NOT NULL GROUP BY \"geocodingProvider\";" ``` --- ## Related Documentation ### Geocoding Documentation - [Geocoding Issues](geocoding-issues.md) - This guide - [Locations Feature](../features/map/locations.md) - Location management - [Data Quality Dashboard](../user-guides/data-quality-dashboard.md) - Monitoring geocoding ### Other Troubleshooting - [Common Errors](common-errors.md) - General errors - [Performance Optimization](performance-optimization.md) - Speed improvements ### External Resources - [Nominatim Usage Policy](https://operations.osmfoundation.org/policies/nominatim/) - [Google Geocoding API](https://developers.google.com/maps/documentation/geocoding) - [Mapbox Geocoding API](https://docs.mapbox.com/api/search/geocoding/) - [Leaflet Documentation](https://leafletjs.com/) --- **Last Updated:** February 2026 **Version:** V2.0 **Status:** Complete