chore: add Kotlin directory structure, update registry.db and gitignore
Añade estructura inicial kotlin/functions/, actualiza registry.db con todos los cambios indexados, y ajusta .gitignore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package infra
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
|
||||
data class POI(
|
||||
val id: Long,
|
||||
val lat: Double,
|
||||
val lon: Double,
|
||||
val name: String,
|
||||
val category: String,
|
||||
val tags: Map<String, String>
|
||||
)
|
||||
|
||||
private val CATEGORY_TAG_MAP: Map<String, String> = mapOf(
|
||||
"restaurant" to "amenity=restaurant",
|
||||
"cafe" to "amenity=cafe",
|
||||
"bar" to "amenity=bar",
|
||||
"museum" to "tourism=museum",
|
||||
"monument" to "historic=monument",
|
||||
"park" to "leisure=park",
|
||||
"library" to "amenity=library",
|
||||
"theatre" to "amenity=theatre",
|
||||
"cinema" to "amenity=cinema",
|
||||
"gallery" to "tourism=gallery",
|
||||
"historic" to "historic",
|
||||
"tourism" to "tourism",
|
||||
"shop" to "shop",
|
||||
"hotel" to "tourism=hotel",
|
||||
"pharmacy" to "amenity=pharmacy",
|
||||
"hospital" to "amenity=hospital"
|
||||
)
|
||||
|
||||
/** Builds the Overpass QL node clause for a single category tag expression. */
|
||||
private fun nodeClause(tagExpr: String, radiusM: Int, lat: Double, lon: Double): String {
|
||||
return if (tagExpr.contains('=')) {
|
||||
val (key, value) = tagExpr.split('=', limit = 2)
|
||||
"""node["$key"="$value"](around:$radiusM,$lat,$lon);"""
|
||||
} else {
|
||||
// Wildcard: match any value for the key
|
||||
"""node["$tagExpr"](around:$radiusM,$lat,$lon);"""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OverpassNearbyPois queries the Overpass API (OpenStreetMap) to retrieve POIs
|
||||
* near the given coordinates within the specified radius.
|
||||
*
|
||||
* Uses only the Android SDK (java.net.HttpURLConnection + org.json) — no external
|
||||
* dependencies. Throws RuntimeException on HTTP or parse errors.
|
||||
*
|
||||
* @param lat Latitude of the center point (WGS84)
|
||||
* @param lon Longitude of the center point (WGS84)
|
||||
* @param radiusM Search radius in metres (default: 500)
|
||||
* @param categories Subset of supported categories to filter by, or null for all
|
||||
* @return List of POIs found, may be empty
|
||||
*/
|
||||
fun overpassNearbyPois(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
radiusM: Int = 500,
|
||||
categories: List<String>? = null
|
||||
): List<POI> {
|
||||
val resolved: Map<String, String> = if (categories == null) {
|
||||
CATEGORY_TAG_MAP
|
||||
} else {
|
||||
CATEGORY_TAG_MAP.filterKeys { it in categories }
|
||||
}
|
||||
|
||||
if (resolved.isEmpty()) {
|
||||
throw RuntimeException("No valid categories resolved from: $categories")
|
||||
}
|
||||
|
||||
val nodeClauses = resolved.values.joinToString("\n ") { tagExpr ->
|
||||
nodeClause(tagExpr, radiusM, lat, lon)
|
||||
}
|
||||
val query = "[timeout:10][out:json];\n(\n $nodeClauses\n);\nout body;"
|
||||
|
||||
val endpoint = "https://overpass-api.de/api/interpreter"
|
||||
val encodedData = "data=" + URLEncoder.encode(query, "UTF-8")
|
||||
|
||||
val conn = (URL(endpoint).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "POST"
|
||||
connectTimeout = 10_000
|
||||
readTimeout = 10_000
|
||||
doOutput = true
|
||||
setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
setRequestProperty("Accept", "application/json")
|
||||
}
|
||||
|
||||
try {
|
||||
conn.outputStream.use { it.write(encodedData.toByteArray(Charsets.UTF_8)) }
|
||||
|
||||
val code = conn.responseCode
|
||||
if (code != 200) {
|
||||
val err = conn.errorStream?.bufferedReader()?.readText() ?: ""
|
||||
throw RuntimeException("Overpass API returned HTTP $code: $err")
|
||||
}
|
||||
|
||||
val body = conn.inputStream.bufferedReader().readText()
|
||||
val root = JSONObject(body)
|
||||
val elements: JSONArray = root.optJSONArray("elements")
|
||||
?: return emptyList()
|
||||
|
||||
val tagExprByCategory: Map<String, String> = resolved.entries
|
||||
.associate { (cat, tagExpr) -> tagExpr to cat }
|
||||
|
||||
val pois = mutableListOf<POI>()
|
||||
for (i in 0 until elements.length()) {
|
||||
val el = elements.getJSONObject(i)
|
||||
if (el.optString("type") != "node") continue
|
||||
|
||||
val id = el.getLong("id")
|
||||
val eLat = el.getDouble("lat")
|
||||
val eLon = el.getDouble("lon")
|
||||
val tagsObj: JSONObject = el.optJSONObject("tags") ?: JSONObject()
|
||||
|
||||
// Build tags map
|
||||
val tagsMap = mutableMapOf<String, String>()
|
||||
for (key in tagsObj.keys()) {
|
||||
tagsMap[key] = tagsObj.getString(key)
|
||||
}
|
||||
|
||||
// Determine category from the first matching tag expression
|
||||
val category = tagExprByCategory.entries.firstOrNull { (tagExpr, _) ->
|
||||
if (tagExpr.contains('=')) {
|
||||
val (k, v) = tagExpr.split('=', limit = 2)
|
||||
tagsMap[k] == v
|
||||
} else {
|
||||
tagsMap.containsKey(tagExpr)
|
||||
}
|
||||
}?.value ?: "unknown"
|
||||
|
||||
val name = tagsMap["name"] ?: category.takeIf { it != "unknown" } ?: "Unknown"
|
||||
|
||||
pois.add(POI(id = id, lat = eLat, lon = eLon, name = name, category = category, tags = tagsMap))
|
||||
}
|
||||
|
||||
return pois
|
||||
} finally {
|
||||
conn.disconnect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user