package infra import org.json.JSONObject import java.net.HttpURLConnection import java.net.URL data class GeocodedLocation( val displayName: String, val street: String, val houseNumber: String, val neighbourhood: String, val city: String, val state: String, val country: String, val postcode: String, val lat: Double, val lon: Double, val osmType: String, val osmId: Long ) /** * nominatimReverseGeocode — Convierte coordenadas lat/lon a una dirección estructurada * usando la API pública de Nominatim (OpenStreetMap). * * @param lat Latitud en grados decimales. * @param lon Longitud en grados decimales. * @param lang Código de idioma ISO 639-1 para la respuesta. Por defecto "es". * @return GeocodedLocation con los campos normalizados de la dirección. * @throws RuntimeException si la petición HTTP falla o el servidor retorna error. */ fun nominatimReverseGeocode(lat: Double, lon: Double, lang: String = "es"): GeocodedLocation { val url = URL( "https://nominatim.openstreetmap.org/reverse" + "?format=jsonv2" + "&lat=$lat" + "&lon=$lon" + "&accept-language=$lang" + "&zoom=18" ) val conn = url.openConnection() as HttpURLConnection conn.setRequestProperty("User-Agent", "fn_registry/1.0") conn.connectTimeout = 5_000 conn.readTimeout = 5_000 conn.requestMethod = "GET" val responseCode = conn.responseCode if (responseCode != HttpURLConnection.HTTP_OK) { conn.disconnect() throw RuntimeException( "nominatimReverseGeocode: HTTP $responseCode para lat=$lat, lon=$lon" ) } val body = try { conn.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } } catch (e: Exception) { conn.disconnect() throw RuntimeException( "nominatimReverseGeocode: error leyendo respuesta — ${e.message}", e ) } finally { conn.disconnect() } val data = JSONObject(body) val address = data.optJSONObject("address") ?: JSONObject() val city = when { address.has("city") && address.getString("city").isNotEmpty() -> address.getString("city") address.has("town") && address.getString("town").isNotEmpty() -> address.getString("town") address.has("village") && address.getString("village").isNotEmpty() -> address.getString("village") else -> "" } val neighbourhood = when { address.has("neighbourhood") && address.getString("neighbourhood").isNotEmpty() -> address.getString("neighbourhood") address.has("suburb") && address.getString("suburb").isNotEmpty() -> address.getString("suburb") else -> "" } return GeocodedLocation( displayName = data.optString("display_name", ""), street = address.optString("road", ""), houseNumber = address.optString("house_number", ""), neighbourhood = neighbourhood, city = city, state = address.optString("state", ""), country = address.optString("country", ""), postcode = address.optString("postcode", ""), lat = data.optString("lat", lat.toString()).toDoubleOrNull() ?: lat, lon = data.optString("lon", lon.toString()).toDoubleOrNull() ?: lon, osmType = data.optString("osm_type", ""), osmId = data.optLong("osm_id", 0L) ) }