defmodule GetGeocode do alias GetGeocode.Apis.{ViaCep, Nominatim} alias GetGeocode.{Geocode, Coords} @moduledoc """ The main module with `get/1` function to retrieve data from CEP (brazilian format), full address format (Nominatim), or a tuple with coordinates `{lat, lng}`. """ @version "0.0.3" @moduledoc since: @version @doc """ Gets geodata from `input`. Returns a tuple with `{:ok, %GetGeocode.Geocode{}}`. ## Examples ``` # CEP format iex> GetGeocode.get "69030000" {:ok, %GetGeocode.Geocode{ city: "Manaus", coords: %GetGeocode.Coords{lat: "-3.1054153", lng: "-60.0547259"}, full_details: "Rua Izaurina Braga, Compensa, Manaus, Região Geográfica Imediata de Manaus, Região Geográfica Intermediária de Manaus, Amazonas, Região Norte, 69000-000, Brasil", neighborhood: "Compensa", postalcode: "69030-000", state: "AM", street: "Rua Izaurina Braga" }} # with full name iex> GetGeocode.get "Rua Compensa, Compensa, Amazonas" {:ok, %GetGeocode.Geocode{ city: "Manaus", coords: %GetGeocode.Coords{lat: "-3.0967331", lng: "-60.0499325"}, full_details: "Rua Guanapuris, Compensa, Manaus, Região Geográfica Imediata de Manaus, Região Geográfica Intermediária de Manaus, Amazonas, Região Norte, 69000-000, Brasil", neighborhood: "Compensa", postalcode: "69000-000", state: "Amazonas", street: "Rua Guanapuris" }} ``` Also works with input being a tuple with coordinates, like `{lat, lng}`. ``` iex> GetGeocode.get {-3.0999329, -60.0552931} {:ok, %GetGeocode.Geocode{ city: "Manaus", coords: %GetGeocode.Coords{lat: "-3.1004858", lng: "-60.0549478"}, full_details: "Rua Boa Esperança, Compensa, Manaus, Região Geográfica Imediata de Manaus, Região Geográfica Intermediária de Manaus, Amazonas, Região Norte, 69000-000, Brasil", neighborhood: "Compensa", postalcode: "69000-000", state: "Amazonas", street: "Rua Boa Esperança" }} ``` """ @doc since: @version def get(input) when is_binary(input) do cond do cep?(input) -> get_viacep(input) addr?(input) -> get_nominatim(input) true -> msg_invalid_input() end end def get(coords) when is_tuple(coords) do Nominatim.get_data(coords) |> builder_from_nominatim() end defp get_viacep(cep) do result = ViaCep.get_cep(cep) case result do %{"erro" => _} -> msg_invalid_input() _ -> builder_from_viacep(result) end end defp get_nominatim(addr) do result = Regex.replace(~r/(.)\1+/, ~s[#{addr}], "\\1") |> Nominatim.get_data() case result do {_, _} -> result _ -> builder_from_nominatim(result) end end defp addr?(addr) do addr |> String.match?(~r/([a-zA-Z]+[\s|,]+[a-zA-Z]+[\s]*)/) end defp cep?(cep) do cep |> String.match?(~r/\b([0-9]{5}-?[0-9]{3})$/) end defp builder_from_viacep(result) do %{ "bairro" => neighborhood, "cep" => postalcode, "localidade" => city, "logradouro" => street, "uf" => state } = result %{ "lon" => lng, "lat" => lat, "display_name" => full_details } = Regex.replace(~r/(.)\1+/, ~s[#{street},#{neighborhood},#{city}], "\\1") |> Nominatim.get_data() {:ok, %Geocode{ coords: %Coords{ lat: lat, lng: lng }, postalcode: postalcode, street: street, neighborhood: neighborhood, city: city, state: state, full_details: full_details }} end defp builder_from_nominatim(result) do %{ "display_name" => full_details, "lat" => lat, "lon" => lng } = result [street, neighborhood, city, _, _, state, _, postalcode | _] = full_details |> String.split(",") |> Enum.map(fn x -> String.trim(x) end) {:ok, %Geocode{ coords: %Coords{ lat: lat, lng: lng }, postalcode: postalcode, street: street, neighborhood: neighborhood, city: city, state: state, full_details: full_details }} end defp msg_invalid_input() do {:error, "Invalid input"} end end