Skip to content

Instantly share code, notes, and snippets.

@matteo-grella
Created November 2, 2017 18:37
Show Gist options
  • Save matteo-grella/a07613d0e04407f770279bbb44abbb07 to your computer and use it in GitHub Desktop.
Save matteo-grella/a07613d0e04407f770279bbb44abbb07 to your computer and use it in GitHub Desktop.
A Kotlin wrapper for the Google Maps Geocoding API v3
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import com.beust.klaxon.*
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.httpGet
import com.github.kittinunf.result.Result
import com.github.kittinunf.result.getAs
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream
class GoogleMapsGeocodingRequest {
private class ResponseError(
val statusCode: Int,
val statusMessage: String,
val errorMessage: String
) : RuntimeException("[%d - %s] %s".format(statusCode, statusMessage, errorMessage))
private val apiURL = "https://maps.googleapis.com/maps/api/geocode/json"
private val googleAPIKey = "" // your APIKey
/**
* @param searchString the string to geocode
*/
fun geocode(searchString: String): GoogleMapsGeocodingParser.ParsedResponse? {
val (_, _, result) = "$apiURL?address=$searchString=&key=$googleAPIKey".httpGet().responseString()
var data: GoogleMapsGeocodingParser.ParsedResponse? = null
when (result) {
is Result.Failure -> onError(result)
is Result.Success -> data = GoogleMapsGeocodingParser.parse(result.getAs<String>()!!)
}
return data
}
/**
*
*/
private fun onError(failureResult: Result.Failure<String, FuelError>) {
throw ResponseError(
statusCode = failureResult.error.response.statusCode,
statusMessage = failureResult.error.response.responseMessage,
errorMessage = failureResult.getErrorMessage())
}
/**
*
*/
private fun Result.Failure<String, FuelError>.getErrorMessage(): String {
return try {
val errorDataInputStream = ByteInputStream(this.error.errorData, this.error.errorData.size)
val jsonErrorData: JsonObject = Parser().parse(errorDataInputStream) as JsonObject
jsonErrorData.string("error_message")!!
} catch (e: RuntimeException) {
""
}
}
}
/**
*
*/
object GoogleMapsGeocodingParser {
data class Coordinates(val lat: Double, val lng: Double)
data class Country(val name: String, val isoCode: String)
data class ParsedResponse(
val address: String,
val coordinates: Coordinates,
val country: Country
)
/**
* The list of allowed types.
*
* See the complete list at https://developers.google.com/maps/documentation/geocoding/intro#GeocodingResponses
*/
private val allowedTyps = setOf(
"street_address",
"route",
"country",
"locality",
"administrative_area_level_1",
"administrative_area_level_2",
"administrative_area_level_3",
"administrative_area_level_4",
"administrative_area_level_5",
"ward",
"airport")
fun parse(googleMapsResponse: String): ParsedResponse? {
val jsonResponse: JsonObject = googleMapsResponse.toJsonObject()
return if (jsonResponse.string("status") != "OK")
null
else try {
jsonResponse.array<JsonObject>("results")!!
.first { it.array<String>("types")!!.toSet().intersect(this.allowedTyps).isNotEmpty() }
.toParsedResponse()
} catch (e: NoSuchElementException) {
null
}
}
private fun String.toJsonObject(): JsonObject {
val byteArray: ByteArray = this.toByteArray()
return Parser().parse(ByteInputStream(byteArray, byteArray.size)) as JsonObject
}
private fun JsonObject.toParsedResponse(): ParsedResponse {
val coordinates: JsonObject = this.obj("geometry")!!.obj("location")!!
val country: JsonObject = this.array<JsonObject>("address_components")!!.first {
"country" in it.array<String>("types")!!
}
return ParsedResponse(
address = this.string("formatted_address")!!,
coordinates = Coordinates(lat = coordinates.double("lat")!!, lng = coordinates.double("lng")!!),
country = Country(name = country.string("long_name")!!, isoCode = country.string("short_name")!!.toLowerCase())
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment