# 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