Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 36x 36x 2x 2x 2x 2x 2x 2x 9x 2x 9x 9x 8x 2x 2x 2x 2x 2x 2x 2x 8x 7x 7x 5x 4x 4x 1x 2x 1x 4x 4x 3x 3x 1x 1x 7x 7x 1x 3x 3x 3x 3x 3x 3x 4x 3x 3x 2x 2x 2x 2x 1x 1x 24x | import type { Logger } from 'pino';
import { connection as redis } from './queueService.server';
import { googleGeocodingService, GoogleGeocodingService } from './googleGeocodingService.server';
import {
nominatimGeocodingService,
NominatimGeocodingService,
} from './nominatimGeocodingService.server';
export class GeocodingService {
constructor(
private googleService: GoogleGeocodingService,
private nominatimService: NominatimGeocodingService,
) {}
async geocodeAddress(
address: string,
logger: Logger,
): Promise<{ lat: number; lng: number } | null> {
const cacheKey = `geocode:${address}`;
try {
const cached = await redis.get(cacheKey);
if (cached) {
logger.info({ cacheKey }, 'Geocoding result found in cache.');
return JSON.parse(cached);
}
} catch (error) {
logger.error({ err: error, cacheKey }, 'Redis GET or JSON.parse command failed. Proceeding without cache.');
}
if (process.env.GOOGLE_MAPS_API_KEY) {
try {
const coordinates = await this.googleService.geocode(address, logger);
if (coordinates) {
await this.setCache(cacheKey, coordinates, logger);
return coordinates;
}
logger.info(
{ address, provider: 'Google' },
'Google Geocoding returned no results. Falling back to Nominatim.',
);
} catch (error) {
logger.error(
{ err: error },
'An error occurred while calling the Google Maps Geocoding API. Falling back to Nominatim.'
);
}
} else {
logger.warn(
'GOOGLE_MAPS_API_KEY is not set. Falling back to Nominatim as the primary geocoding provider.',
);
}
const nominatimResult = await this.nominatimService.geocode(address, logger);
if (nominatimResult) {
await this.setCache(cacheKey, nominatimResult, logger);
return nominatimResult;
}
logger.error({ address }, 'All geocoding providers failed.');
return null;
}
private async setCache(
cacheKey: string,
result: { lat: number; lng: number },
logger: Logger,
): Promise<void> {
try {
await redis.set(cacheKey, JSON.stringify(result), 'EX', 60 * 60 * 24 * 30); // Cache for 30 days
} catch (error) {
logger.error({ err: error, cacheKey }, 'Redis SET command failed. Result will not be cached.');
}
}
async clearGeocodeCache(logger: Logger): Promise<number> {
let cursor = '0';
let totalDeleted = 0;
const pattern = 'geocode:*';
logger.info(`Starting geocode cache clear with pattern: ${pattern}`);
try {
do {
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
cursor = nextCursor;
if (keys.length > 0) {
const deletedCount = await redis.del(keys);
totalDeleted += deletedCount;
}
} while (cursor !== '0');
logger.info(`Successfully deleted ${totalDeleted} geocode cache entries.`);
return totalDeleted;
} catch (error) {
logger.error({ err: error }, 'Failed to clear geocode cache from Redis.');
throw error;
}
}
}
export const geocodingService = new GeocodingService(
googleGeocodingService,
nominatimGeocodingService,
);
|