export type GeocodeParams = {
location?: string
latitude?: number
longitude?: number
label?: string
}
export type GeocodeResult = {
latitude: number
longitude: number
displayName: string
source: 'coordinates' | 'geocoded'
query?: string
}
function clean(value: unknown): string {
return String(value || '').trim()
}
function normalizeCoordinate(value: unknown, label: string): number {
const numeric = Number(value)
if (!Number.isFinite(numeric)) {
throw new Error('Invalid ' + label + ' coordinate.')
}
return numeric
}
/** Resolve a free-form place name or direct coordinates into reusable coordinates. */
export async function resolveWeatherLocation(
params: GeocodeParams = {},
): Promise<GeocodeResult> {
const input = typeof params === 'object' && params ? params : {}
const latitudeProvided = input.latitude !== undefined && input.latitude !== null
const longitudeProvided =
input.longitude !== undefined && input.longitude !== null
if (latitudeProvided || longitudeProvided) {
if (!latitudeProvided || !longitudeProvided) {
throw new Error(
'Provide both latitude and longitude when using direct coordinates.',
)
}
const latitude = normalizeCoordinate(input.latitude, 'latitude')
const longitude = normalizeCoordinate(input.longitude, 'longitude')
return {
latitude,
longitude,
displayName: clean(input.label) || latitude + ', ' + longitude,
source: 'coordinates',
}
}
const location = clean(input.location)
if (!location) {
throw new Error('Provide either a location string or direct coordinates.')
}
const response = await fetch(
'https://nominatim.openstreetmap.org/search?q=' +
encodeURIComponent(location) +
'&format=json&limit=1',
{
headers: {
'User-Agent': 'Kody/1.0 (kody.codes)',
},
},
)
const data = await response.json().catch(() => [])
if (!response.ok || !Array.isArray(data) || data.length === 0) {
throw new Error('Could not geocode location: "' + location + '"')
}
const first = data[0]
return {
latitude: Number(first.lat),
longitude: Number(first.lon),
displayName: clean(first.display_name) || location,
source: 'geocoded',
query: location,
}
}
export default resolveWeatherLocation