105 lines
3 KiB
Elixir
105 lines
3 KiB
Elixir
defmodule Towerops.Geocoding do
|
|
@moduledoc """
|
|
Service for geocoding addresses using Google Maps Geocoding API.
|
|
|
|
Supports both system-wide API keys and organization-specific encrypted API keys.
|
|
"""
|
|
|
|
alias Towerops.HTTP
|
|
|
|
require Logger
|
|
|
|
@google_geocoding_url "https://maps.googleapis.com/maps/api/geocode/json"
|
|
|
|
@doc """
|
|
Geocode an address to latitude/longitude coordinates.
|
|
|
|
## Parameters
|
|
|
|
- `address`: The address string to geocode
|
|
|
|
## Returns
|
|
|
|
- `{:ok, %{latitude: float, longitude: float, formatted_address: string}}` on success
|
|
- `{:error, reason}` on failure
|
|
"""
|
|
def geocode(address) when is_binary(address) do
|
|
with {:ok, api_key} <- get_api_key(),
|
|
{:ok, response} <- make_geocoding_request(address, api_key) do
|
|
parse_geocoding_response(response)
|
|
end
|
|
end
|
|
|
|
defp get_api_key do
|
|
api_key = get_system_api_key()
|
|
|
|
if api_key && String.trim(api_key) != "" do
|
|
{:ok, String.trim(api_key)}
|
|
else
|
|
{:error, :no_api_key}
|
|
end
|
|
end
|
|
|
|
defp get_system_api_key do
|
|
Application.get_env(:towerops, :google_maps_api_key) ||
|
|
System.get_env("GOOGLE_MAPS_API_KEY")
|
|
end
|
|
|
|
defp make_geocoding_request(address, api_key) do
|
|
params = %{
|
|
"address" => address,
|
|
"key" => api_key
|
|
}
|
|
|
|
case HTTP.get(__MODULE__, @google_geocoding_url, params: params, timeout: 10_000) do
|
|
{:ok, %Req.Response{status: 200, body: body}} ->
|
|
{:ok, body}
|
|
|
|
{:ok, %Req.Response{status: status_code, body: body}} ->
|
|
Logger.warning("Geocoding API returned status #{status_code}: #{inspect(body)}")
|
|
{:error, "Geocoding API error (status #{status_code})"}
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Failed to make geocoding request: #{inspect(reason)}")
|
|
{:error, "Network error: #{inspect(reason)}"}
|
|
end
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "OK", "results" => [result | _]}) do
|
|
with %{"geometry" => %{"location" => %{"lat" => lat, "lng" => lng}}} <- result,
|
|
%{"formatted_address" => formatted_address} <- result do
|
|
{:ok,
|
|
%{
|
|
latitude: lat,
|
|
longitude: lng,
|
|
formatted_address: formatted_address
|
|
}}
|
|
else
|
|
_ -> {:error, "Invalid response format from Google Maps API"}
|
|
end
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "ZERO_RESULTS"}) do
|
|
{:error, "No results found for the given address"}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "OVER_QUERY_LIMIT"}) do
|
|
{:error, "API quota exceeded. Please check your Google Maps API usage."}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "REQUEST_DENIED", "error_message" => message}) do
|
|
{:error, "API request denied: #{message}"}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => "INVALID_REQUEST"}) do
|
|
{:error, "Invalid request. Please check the address format."}
|
|
end
|
|
|
|
defp parse_geocoding_response(%{"status" => status}) do
|
|
{:error, "Geocoding failed with status: #{status}"}
|
|
end
|
|
|
|
defp parse_geocoding_response(_response) do
|
|
{:error, "Unexpected response format from Google Maps API"}
|
|
end
|
|
end
|