34 KiB

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

# 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

# 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

# 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

# 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:

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

# 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

# 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

# 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

# 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)
# 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

# 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

# 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:

# 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

# 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:

# 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

# 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

# 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

# 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

# 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:

// 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:

# Allow OSM tiles
add_header Content-Security-Policy "... img-src 'self' data: https://*.openstreetmap.org;";

Solution 5: Try alternative tile provider

// In map component
<TileLayer
  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
  // Or try Carto:
  // url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"
/>

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

// 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

# 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

// 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

// Verify CircleMarker component
{locations.map((location) => {
  if (!location.latitude || !location.longitude) return null;

  return (
    <CircleMarker
      key={location.id}
      center={[location.latitude, location.longitude]}
      radius={8}
      // ...
    />
  );
})}

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

# 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

// 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

-- 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

// 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

// Verify Polygon rendering
<Polygon
  positions={coords}  // Array of [lat, lng]
  pathOptions={{
    color: '#3498db',
    fillColor: '#3498db',
    fillOpacity: 0.2
  }}
/>

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:

# 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

// 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

// In geolocate code
navigator.geolocation.getCurrentPosition(
  successCallback,
  errorCallback,
  {
    timeout: 10000,  // 10 seconds (default: 5000)
    enableHighAccuracy: true,
    maximumAge: 0
  }
);

Solution 5: Fallback to IP geolocation

// 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
# 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

# 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)
# 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

# 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

// 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)
);

<MapContainer
  maxBounds={bounds}
  maxBoundsViscosity={1.0}
  // ...
/>

Solution 2: Filter locations by bounds

// 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:

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

# 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:

# 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

# 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

# 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

# Restart API (restarts worker)
docker compose restart api

# Check worker started
docker compose logs api | grep "Geocoding worker started"

Solution 4: Check Redis connection

# 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

# 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

# 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

# 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

# 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:

// 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:

# Google Batch Geocoding (requires Business plan)
# Can geocode up to 100 addresses in one request

Solution 4: Cache results

// 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

// 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

# 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:

// 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

// 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

# 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

# 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

# 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

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

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

# 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

# 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

# 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

# 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\";"

Geocoding Documentation

Other Troubleshooting

External Resources


Last Updated: February 2026 Version: V2.0 Status: Complete