Files
TrisolarisServer/src/main/kotlin/com/android/trisolarisserver/component/GoogleGeocodingClient.kt
androidlover5842 3b2733e7cb
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s
Record pincode request URLs and harden fetches
2026-01-31 12:11:22 +05:30

111 lines
4.4 KiB
Kotlin

package com.android.trisolarisserver.component
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder
@Component
class GoogleGeocodingClient(
private val restTemplate: RestTemplate,
private val objectMapper: ObjectMapper,
@Value("\${google.maps.apiKey:}")
private val apiKey: String,
@Value("\${google.maps.geocode.baseUrl:https://maps.googleapis.com/maps/api/geocode/json}")
private val baseUrl: String
) {
private val logger = LoggerFactory.getLogger(GoogleGeocodingClient::class.java)
fun resolveCityState(pinCode: String): GeocodeResult {
if (apiKey.isBlank()) {
return GeocodeResult(null, null, "NO_API_KEY")
}
return try {
val url = UriComponentsBuilder.fromUriString(baseUrl)
.queryParam("components", "postal_code:$pinCode|country:IN")
.queryParam("region", "IN")
.queryParam("key", apiKey)
.toUriString()
val primary = fetch(url)
if (primary.status == "OK") {
return primary
}
if (primary.status == "ZERO_RESULTS") {
val fallbackUrl = UriComponentsBuilder.fromUriString(baseUrl)
.queryParam("address", "$pinCode India")
.queryParam("region", "IN")
.queryParam("key", apiKey)
.toUriString()
val fallback = fetch(fallbackUrl)
if (fallback.resolvedCityState != null) return fallback
return primary
}
primary
} catch (ex: Exception) {
logger.warn("Geocoding failed: {}", ex.message)
GeocodeResult(null, null, "ERROR")
}
}
private fun fetch(url: String): GeocodeResult {
val response = restTemplate.getForEntity(url, String::class.java)
val body = response.body ?: return GeocodeResult(null, null, "EMPTY_BODY")
val status = parseStatus(body)
val parsed = if (status == "OK") parseCityState(body) else null
return GeocodeResult(parsed, body, status ?: "UNKNOWN_STATUS")
}
private fun parseStatus(body: String): String? {
return objectMapper.readTree(body).path("status").asText(null)
}
private fun parseCityState(body: String): String? {
val root = objectMapper.readTree(body)
val results = root.path("results")
if (!results.isArray || results.isEmpty) return null
val resultNode = results.firstOrNull { node ->
node.path("types").any { it.asText(null) == "postal_code" }
} ?: results.first()
val components = resultNode.path("address_components")
if (!components.isArray) return null
var city: String? = null
var admin2: String? = null
var state: String? = null
for (comp in components) {
val types = comp.path("types").mapNotNull { it.asText(null) }.toSet()
when {
"locality" in types -> city = comp.path("long_name").asText(null) ?: city
"postal_town" in types -> if (city == null) {
city = comp.path("long_name").asText(null)
}
"sublocality" in types -> if (city == null) {
city = comp.path("long_name").asText(null)
}
"administrative_area_level_2" in types -> admin2 = comp.path("long_name").asText(null) ?: admin2
"administrative_area_level_1" in types -> state = comp.path("long_name").asText(null) ?: state
}
}
val preferredCity = admin2?.trim()?.ifBlank { null } ?: city?.trim()?.ifBlank { null }
if (preferredCity == null && state.isNullOrBlank()) return null
return listOfNotNull(preferredCity, state?.trim()?.ifBlank { null }).joinToString(", ")
}
}
data class GeocodeResult(
val resolvedCityState: String?,
val rawResponse: String?,
val status: String?
)
data class PincodeLookupResult(
val resolvedCityState: String?,
val rawResponse: String?,
val status: String?,
val source: String,
val errorMessage: String? = null,
val requestUrl: String? = null
)