Packages

Elixir client for Google Geolocation and Elevation APIs

Current section

Files

Jump to
geolocation_client lib geolocation_client.ex
Raw

lib/geolocation_client.ex

defmodule GeolocationClient do
@moduledoc """
Client for Google Geolocation API
"""
@base_url "https://www.googleapis.com/"
@doc """
Set API key
"""
@spec set_api_key(String.t()) :: :ok
def set_api_key(key) do
Application.put_env(:geolocation_client, :api_key, key)
end
@doc """
Geolocate from a list of MAC addresses
"""
@spec geolocate_from_mac_addresses([GeolocationClient.Entities.MacAddress.t()], boolean()) ::
GeolocationClient.Entities.LatLon.t() | {:error, any()}
def geolocate_from_mac_addresses(mac_addresses, consider_ip \\ false)
when is_list(mac_addresses) do
# Implementation would go here
url =
"#{@base_url}geolocation/v1/geolocate?key=#{Application.get_env(:geolocation_client, :api_key)}"
body = %{
"considerIp" => consider_ip,
"wifiAccessPoints" =>
Enum.map(mac_addresses, fn mac ->
%{
"macAddress" => mac.mac_address,
"signalStrength" => mac.signal_strength
}
end)
}
case Req.post(url, json: body) do
{:ok, %Req.Response{status: 200, body: %{"location" => location, "accuracy" => accuracy}}} ->
%GeolocationClient.Entities.LatLon{
latitude: location["lat"],
longitude: location["lng"],
accuracy: accuracy
}
{:ok, %Req.Response{status: status, body: body}} ->
{:error, %{status: status, body: body}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Get altitude for given latitude and longitude
"""
@spec get_altitude(GeolocationClient.Entities.LatLon.t()) :: float() | {:error, any()}
def get_altitude(%GeolocationClient.Entities.LatLon{latitude: lat, longitude: lon}) do
# Implementation would go here
url =
"https://maps.googleapis.com/maps/api/elevation/json?locations=#{lat}%2C#{lon}&key=#{Application.get_env(:geolocation_client, :api_key)}"
case Req.get(url) do
{:ok, %Req.Response{status: 200, body: %{"results" => [%{"elevation" => elevation} | _]}}} ->
elevation
{:ok, %Req.Response{status: status, body: body}} ->
{:error, %{status: status, body: body}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Geolocate from mac addresses and get full location (LLA)
"""
@spec geolocate_full_location([GeolocationClient.Entities.MacAddress.t()], boolean()) ::
GeolocationClient.Entities.Location.t() | {:error, any()}
def geolocate_full_location(mac_addresses, consider_ip \\ false) when is_list(mac_addresses) do
case geolocate_from_mac_addresses(mac_addresses, consider_ip) do
%GeolocationClient.Entities.LatLon{latitude: lat, longitude: lon} = lat_lon ->
case get_altitude(lat_lon) do
elevation when is_float(elevation) ->
%GeolocationClient.Entities.Location{
latitude: lat,
longitude: lon,
altitude: elevation
}
{:error, _} = error ->
error
end
{:error, _} = error ->
error
end
end
@doc """
Reverse geocode from latitude and longitude to address
"""
@spec reverse_geocode(GeolocationClient.Entities.LatLon.t()) ::
GeolocationClient.Entities.Geocode.t() | {:error, any()}
def reverse_geocode(%GeolocationClient.Entities.LatLon{latitude: lat, longitude: lon}) do
url =
"https://maps.googleapis.com/maps/api/geocode/json?latlng=#{lat},#{lon}&key=#{Application.get_env(:geolocation_client, :api_key)}"
case Req.get(url) do
{:ok, %Req.Response{status: 200, body: %{"results" => [result | _]}}} ->
address_components = result["address_components"] || []
# Create the Geocode struct with all possible address components
%GeolocationClient.Entities.Geocode{
street_number: find_component(address_components, "street_number"),
street_address: find_component(address_components, "street_address"),
route: find_component(address_components, "route"),
intersection: find_component(address_components, "intersection"),
political: find_component(address_components, "political"),
country: find_component(address_components, "country"),
administrative_area_level_1:
find_component(address_components, "administrative_area_level_1"),
administrative_area_level_2:
find_component(address_components, "administrative_area_level_2"),
administrative_area_level_3:
find_component(address_components, "administrative_area_level_3"),
administrative_area_level_4:
find_component(address_components, "administrative_area_level_4"),
administrative_area_level_5:
find_component(address_components, "administrative_area_level_5"),
administrative_area_level_6:
find_component(address_components, "administrative_area_level_6"),
administrative_area_level_7:
find_component(address_components, "administrative_area_level_7"),
colloquial_area: find_component(address_components, "colloquial_area"),
locality: find_component(address_components, "locality"),
sublocality: find_component(address_components, "sublocality"),
sublocality_level_1: find_component(address_components, "sublocality_level_1"),
sublocality_level_2: find_component(address_components, "sublocality_level_2"),
sublocality_level_3: find_component(address_components, "sublocality_level_3"),
sublocality_level_4: find_component(address_components, "sublocality_level_4"),
sublocality_level_5: find_component(address_components, "sublocality_level_5"),
neighborhood: find_component(address_components, "neighborhood"),
premise: find_component(address_components, "premise"),
subpremise: find_component(address_components, "subpremise"),
plus_code: find_component(address_components, "plus_code"),
postal_code: find_component(address_components, "postal_code"),
natural_feature: find_component(address_components, "natural_feature"),
airport: find_component(address_components, "airport"),
park: find_component(address_components, "park"),
point_of_interest: find_component(address_components, "point_of_interest"),
formatted_address: result["formatted_address"]
}
{:ok, %Req.Response{status: status, body: body}} ->
{:error, %{status: status, body: body}}
{:error, reason} ->
{:error, reason}
end
end
# Find address components by type - response lists types per component
defp find_component(address_components, type) do
component =
Enum.find(address_components, fn component ->
Enum.member?(component["types"], type)
end)
case component do
nil -> nil
_ -> component["long_name"]
end
end
end