import axios, { AxiosResponse } from 'axios';
import type {
GeocodeResponse,
ZipGeocodeResponse,
LocationInput,
} from '../types/weather.js';
export class GeocodingService {
private readonly apiKey: string;
private readonly baseUrl = 'http://api.openweathermap.org/geo/1.0';
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async getCoordinatesByLocationName(
query: string,
limit = 5
): Promise<GeocodeResponse[]> {
try {
const response: AxiosResponse<GeocodeResponse[]> = await axios.get(
`${this.baseUrl}/direct`,
{
params: {
q: query,
limit,
appid: this.apiKey,
},
}
);
return response.data;
} catch (error) {
throw new Error(
`Failed to geocode location "${query}": ${
error instanceof Error ? error.message : 'Unknown error'
}`
);
}
}
async getCoordinatesByZipCode(
zipCode: string,
countryCode = 'US'
): Promise<ZipGeocodeResponse> {
try {
const zipQuery = countryCode ? `${zipCode},${countryCode}` : zipCode;
const response: AxiosResponse<ZipGeocodeResponse> = await axios.get(
`${this.baseUrl}/zip`,
{
params: {
zip: zipQuery,
appid: this.apiKey,
},
}
);
return response.data;
} catch (error) {
throw new Error(
`Failed to geocode zip code "${zipCode}": ${
error instanceof Error ? error.message : 'Unknown error'
}`
);
}
}
async resolveLocation(
input: LocationInput
): Promise<{ lat: number; lon: number }> {
if (input.lat !== undefined && input.lon !== undefined) {
return { lat: input.lat, lon: input.lon };
}
if (input.zipCode) {
const result = await this.getCoordinatesByZipCode(
input.zipCode,
input.country
);
return { lat: result.lat, lon: result.lon };
}
if (input.city) {
let query = input.city;
if (input.state) {
query += `, ${input.state}`;
}
if (input.country) {
query += `, ${input.country}`;
}
const results = await this.getCoordinatesByLocationName(query, 1);
if (results.length === 0) {
throw new Error(`No location found for "${query}"`);
}
return { lat: results[0].lat, lon: results[0].lon };
}
throw new Error(
'Invalid location input. Provide coordinates, zip code, or city name.'
);
}
}