io.taig.taigless.geo.GoogleMapsGeo.scala Maven / Gradle / Ivy
package io.taig.taigless.geo
import cats.effect.kernel.{Async, Resource}
import cats.syntax.all._
import com.google.maps._
import com.google.maps.errors.InvalidRequestException
import com.google.maps.model.{Unit => _, _}
import java.net.URL
import java.time.ZoneId
import java.util.TimeZone
import scala.util.chaining._
final class GoogleMapsGeo[F[_]](context: GeoApiContext)(implicit F: Async[F]) extends Geo[F, GoogleMapsGeo.PlaceId] {
override def find(query: String, locale: Option[String]): F[List[Geo.Location[GoogleMapsGeo.PlaceId]]] =
F.async_[Array[GeocodingResult]] { callback =>
GeocodingApi
.geocode(context, query)
.pipe { request =>
locale.fold(request)(request.language)
}
.components(ComponentFilter.country("de"))
.resultType(AddressType.STREET_ADDRESS)
.setCallback(new PendingResult.Callback[Array[GeocodingResult]] {
override def onResult(result: Array[GeocodingResult]): Unit = callback(Right(result))
override def onFailure(throwable: Throwable): Unit = callback(Left(throwable))
})
}.map { results =>
results.toList.map { result =>
val formatted = result.formattedAddress
val position = toPosition(result.geometry.location)
val url = new URL(GoogleMaps.locationUrl(formatted, position))
Geo.Location(GoogleMapsGeo.PlaceId(result.placeId), formatted, position, url)
}
}
override def findByIdentifier(
identifier: GoogleMapsGeo.PlaceId,
locale: Option[String]
): F[Option[Geo.Location[GoogleMapsGeo.PlaceId]]] =
F.async_[Option[PlaceDetails]] { callback =>
PlacesApi
.placeDetails(context, identifier.value)
.pipe(request => locale.fold(request)(request.language))
.setCallback(new PendingResult.Callback[PlaceDetails] {
override def onResult(result: PlaceDetails): Unit = callback(Right(Some(result)))
override def onFailure(throwable: Throwable): Unit = callback(Left(throwable))
})
}.recover { case _: InvalidRequestException => None }
.map {
case Some(details) =>
Some(Geo.Location(identifier, details.formattedAddress, toPosition(details.geometry.location), details.url))
case None => None
}
override def timezone(position: Position): F[ZoneId] =
F.async_[TimeZone] { callback =>
TimeZoneApi
.getTimeZone(context, toLatLng(position))
.setCallback(new PendingResult.Callback[TimeZone] {
override def onResult(result: TimeZone): Unit = callback(Right(result))
override def onFailure(throwable: Throwable): Unit = callback(Left(throwable))
})
}.map(_.toZoneId)
def toLatLng(position: Position): LatLng = new LatLng(position.latitude.value, position.longitude.value)
def toPosition(latLng: LatLng): Position = Position(Latitude(normalize(latLng.lat)), Longitude(normalize(latLng.lng)))
def normalize(axis: Double): Double =
BigDecimal.valueOf(axis).setScale(7, BigDecimal.RoundingMode.HALF_UP).doubleValue
}
object GoogleMapsGeo {
final case class PlaceId(value: String) extends AnyVal
def apply[F[_]: Async](context: GeoApiContext): Geo[F, PlaceId] = new GoogleMapsGeo[F](context)
def fromApiKey[F[_]](key: String)(implicit F: Async[F]): Resource[F, Geo[F, PlaceId]] = {
val acquire = F.blocking(new GeoApiContext.Builder().apiKey(key).build())
Resource.make(acquire)(context => F.blocking(context.shutdown())).map(GoogleMapsGeo[F])
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy