All files / src/services nominatimGeocodingService.server.ts

63.15% Statements 36/57
90.9% Branches 10/11
50% Functions 1/2
71.42% Lines 30/42

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 432x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 2x 4x 4x 3x 2x 2x 2x 1x 1x       1x   1x 1x   2x       2x         30x  
import type { Logger } from 'pino';
import { logger as defaultLogger } from './logger.server';
 
export class NominatimGeocodingService {
  /**
   * Geocodes an address using the public Nominatim API.
   * @param address The address string to geocode.
   * @param logger A logger instance.
   * @returns A promise that resolves to the coordinates or null if not found.
   */
  async geocode(
    address: string,
    logger: Logger = defaultLogger,
  ): Promise<{ lat: number; lng: number } | null> {
    const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json&limit=1`;
 
    try {
      const response = await fetch(url, { headers: { 'User-Agent': 'FlyerCrawler/1.0' } });
      if (!response.ok) throw new Error(`Nominatim API returned status ${response.status}`);
 
      const data = await response.json();
      if (data && data.length > 0) {
        const { lat, lon } = data[0];
        logger.info(
          { address, result: { lat, lon } },
          `[NominatimService] Successfully geocoded address`,
        );
        return { lat: parseFloat(lat), lng: parseFloat(lon) };
      }
      logger.warn({ address }, '[NominatimService] Geocoding failed or returned no results.');
      return null;
    } catch (error) {
      logger.error(
        { err: error, address },
        '[NominatimService] An error occurred while calling the Nominatim API.',
      );
      return null;
    }
  }
}

export const nominatimGeocodingService = new NominatimGeocodingService();