Add GeoSpatial backends for geocoding
Signed-off-by: Thomas Citharel <tcit@tcit.fr> Geospatial Backend Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
85
lib/service/geospatial/addok.ex
Normal file
85
lib/service/geospatial/addok.ex
Normal file
@@ -0,0 +1,85 @@
|
||||
defmodule Mobilizon.Service.Geospatial.Addok do
|
||||
@moduledoc """
|
||||
[Addok](https://github.com/addok/addok) backend.
|
||||
"""
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
require Logger
|
||||
alias Mobilizon.Addresses.Address
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@endpoint Application.get_env(:mobilizon, __MODULE__) |> get_in([:endpoint])
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Addok implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
"""
|
||||
@spec geocode(String.t(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
url = build_url(:geocode, %{lon: lon, lat: lat}, options)
|
||||
|
||||
Logger.debug("Asking addok for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"features" => features}} <- Poison.decode(body) do
|
||||
processData(features)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Addok implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
url = build_url(:search, %{q: q}, options)
|
||||
Logger.debug("Asking addok for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"features" => features}} <- Poison.decode(body) do
|
||||
processData(features)
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_url(atom(), map(), list()) :: String.t()
|
||||
defp build_url(method, args, options) do
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
coords = Keyword.get(options, :coords, nil)
|
||||
endpoint = Keyword.get(options, :endpoint, @endpoint)
|
||||
|
||||
case method do
|
||||
:geocode ->
|
||||
"#{endpoint}/reverse/?lon=#{args.lon}&lat=#{args.lat}&limit=#{limit}"
|
||||
|
||||
:search ->
|
||||
url = "#{endpoint}/search/?q=#{URI.encode(args.q)}&limit=#{limit}"
|
||||
if is_nil(coords), do: url, else: url <> "&lat=#{coords.lat}&lon=#{coords.lon}"
|
||||
end
|
||||
end
|
||||
|
||||
defp processData(features) do
|
||||
features
|
||||
|> Enum.map(fn %{"geometry" => geometry, "properties" => properties} ->
|
||||
%Address{
|
||||
addressCountry: Map.get(properties, "country"),
|
||||
addressLocality: Map.get(properties, "city"),
|
||||
addressRegion: Map.get(properties, "state"),
|
||||
description: Map.get(properties, "name") || streetAddress(properties),
|
||||
floor: Map.get(properties, "floor"),
|
||||
geom: Map.get(geometry, "coordinates") |> Provider.coordinates(),
|
||||
postalCode: Map.get(properties, "postcode"),
|
||||
streetAddress: properties |> streetAddress()
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp streetAddress(properties) do
|
||||
if Map.has_key?(properties, "housenumber") do
|
||||
Map.get(properties, "housenumber") <> " " <> Map.get(properties, "street")
|
||||
else
|
||||
Map.get(properties, "street")
|
||||
end
|
||||
end
|
||||
end
|
||||
15
lib/service/geospatial/geospatial.ex
Normal file
15
lib/service/geospatial/geospatial.ex
Normal file
@@ -0,0 +1,15 @@
|
||||
defmodule Mobilizon.Service.Geospatial do
|
||||
@moduledoc """
|
||||
Module to load the service adapter defined inside the configuration
|
||||
|
||||
See `Mobilizon.Service.Geospatial.Provider`
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Returns the appropriate service adapter
|
||||
|
||||
According to the config behind `config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Module`
|
||||
"""
|
||||
@spec service() :: module()
|
||||
def service(), do: Application.get_env(:mobilizon, __MODULE__) |> get_in([:service])
|
||||
end
|
||||
126
lib/service/geospatial/google_maps.ex
Normal file
126
lib/service/geospatial/google_maps.ex
Normal file
@@ -0,0 +1,126 @@
|
||||
defmodule Mobilizon.Service.Geospatial.GoogleMaps do
|
||||
@moduledoc """
|
||||
Google Maps [Geocoding service](https://developers.google.com/maps/documentation/geocoding/intro)
|
||||
|
||||
Note: Endpoint is hardcoded to Google Maps API
|
||||
"""
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
alias Mobilizon.Addresses.Address
|
||||
require Logger
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@api_key Application.get_env(:mobilizon, __MODULE__) |> get_in([:api_key])
|
||||
|
||||
@components [
|
||||
"street_number",
|
||||
"route",
|
||||
"locality",
|
||||
"administrative_area_level_1",
|
||||
"country",
|
||||
"postal_code"
|
||||
]
|
||||
|
||||
@api_key_missing_message "API Key required to use Google Maps"
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Google Maps implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
"""
|
||||
@spec geocode(String.t(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
url = build_url(:geocode, %{lon: lon, lat: lat}, options)
|
||||
|
||||
Logger.debug("Asking Google Maps for reverse geocode with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"results" => results, "status" => "OK"}} <- Poison.decode(body) do
|
||||
Enum.map(results, &process_data/1)
|
||||
else
|
||||
{:ok, %{"status" => "REQUEST_DENIED", "error_message" => error_message}} ->
|
||||
raise ArgumentError, message: to_string(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Google Maps implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
url = build_url(:search, %{q: q}, options)
|
||||
|
||||
Logger.debug("Asking Google Maps for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"results" => results, "status" => "OK"}} <- Poison.decode(body) do
|
||||
Enum.map(results, fn entry -> process_data(entry) end)
|
||||
else
|
||||
{:ok, %{"status" => "REQUEST_DENIED", "error_message" => error_message}} ->
|
||||
raise ArgumentError, message: to_string(error_message)
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_url(atom(), map(), list()) :: String.t()
|
||||
defp build_url(method, args, options) do
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
lang = Keyword.get(options, :lang, "en")
|
||||
api_key = Keyword.get(options, :api_key, @api_key)
|
||||
if is_nil(api_key), do: raise(ArgumentError, message: @api_key_missing_message)
|
||||
|
||||
url =
|
||||
"https://maps.googleapis.com/maps/api/geocode/json?limit=#{limit}&key=#{api_key}&language=#{
|
||||
lang
|
||||
}"
|
||||
|
||||
case method do
|
||||
:search ->
|
||||
url <> "&address=#{URI.encode(args.q)}"
|
||||
|
||||
:geocode ->
|
||||
url <> "&latlng=#{args.lat},#{args.lon}"
|
||||
end
|
||||
end
|
||||
|
||||
defp process_data(%{
|
||||
"formatted_address" => description,
|
||||
"geometry" => %{"location" => %{"lat" => lat, "lng" => lon}},
|
||||
"address_components" => components
|
||||
}) do
|
||||
components =
|
||||
@components
|
||||
|> Enum.reduce(%{}, fn component, acc ->
|
||||
Map.put(acc, component, extract_component(components, component))
|
||||
end)
|
||||
|
||||
%Address{
|
||||
addressCountry: Map.get(components, "country"),
|
||||
addressLocality: Map.get(components, "locality"),
|
||||
addressRegion: Map.get(components, "administrative_area_level_1"),
|
||||
description: description,
|
||||
floor: nil,
|
||||
geom: [lon, lat] |> Provider.coordinates(),
|
||||
postalCode: Map.get(components, "postal_code"),
|
||||
streetAddress: street_address(components)
|
||||
}
|
||||
end
|
||||
|
||||
defp extract_component(components, key) do
|
||||
case components
|
||||
|> Enum.filter(fn component -> key in component["types"] end)
|
||||
|> Enum.map(& &1["long_name"]) do
|
||||
[] -> nil
|
||||
component -> hd(component)
|
||||
end
|
||||
end
|
||||
|
||||
defp street_address(body) do
|
||||
if Map.has_key?(body, "street_number") && !is_nil(Map.get(body, "street_number")) do
|
||||
Map.get(body, "street_number") <> " " <> Map.get(body, "route")
|
||||
else
|
||||
Map.get(body, "route")
|
||||
end
|
||||
end
|
||||
end
|
||||
116
lib/service/geospatial/map_quest.ex
Normal file
116
lib/service/geospatial/map_quest.ex
Normal file
@@ -0,0 +1,116 @@
|
||||
defmodule Mobilizon.Service.Geospatial.MapQuest do
|
||||
@moduledoc """
|
||||
[MapQuest](https://developer.mapquest.com/documentation) backend.
|
||||
|
||||
## Options
|
||||
In addition to the [the shared options](Mobilizon.Service.Geospatial.Provider.html#module-shared-options),
|
||||
MapQuest methods support the following options:
|
||||
* `:open_data` Whether to use [Open Data or Licenced Data](https://developer.mapquest.com/documentation/open/).
|
||||
Defaults to `true`
|
||||
"""
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
alias Mobilizon.Addresses.Address
|
||||
require Logger
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@api_key Application.get_env(:mobilizon, __MODULE__) |> get_in([:api_key])
|
||||
|
||||
@api_key_missing_message "API Key required to use MapQuest"
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
MapQuest implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
"""
|
||||
@spec geocode(String.t(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
api_key = Keyword.get(options, :api_key, @api_key)
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
open_data = Keyword.get(options, :open_data, true)
|
||||
|
||||
prefix = if open_data, do: "open", else: "www"
|
||||
|
||||
if is_nil(api_key), do: raise(ArgumentError, message: @api_key_missing_message)
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(
|
||||
"https://#{prefix}.mapquestapi.com/geocoding/v1/reverse?key=#{api_key}&location=#{
|
||||
lat
|
||||
},#{lon}&maxResults=#{limit}"
|
||||
),
|
||||
{:ok, %{"results" => results, "info" => %{"statuscode" => 0}}} <- Poison.decode(body) do
|
||||
results |> Enum.map(&processData/1)
|
||||
else
|
||||
{:ok, %HTTPoison.Response{status_code: 403, body: err}} ->
|
||||
raise(ArgumentError, message: err)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
MapQuest implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
api_key = Keyword.get(options, :api_key, @api_key)
|
||||
|
||||
open_data = Keyword.get(options, :open_data, true)
|
||||
|
||||
prefix = if open_data, do: "open", else: "www"
|
||||
|
||||
if is_nil(api_key), do: raise(ArgumentError, message: @api_key_missing_message)
|
||||
|
||||
url =
|
||||
"https://#{prefix}.mapquestapi.com/geocoding/v1/address?key=#{api_key}&location=#{
|
||||
URI.encode(q)
|
||||
}&maxResults=#{limit}"
|
||||
|
||||
Logger.debug("Asking MapQuest for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"results" => results, "info" => %{"statuscode" => 0}}} <- Poison.decode(body) do
|
||||
results |> Enum.map(&processData/1)
|
||||
else
|
||||
{:ok, %HTTPoison.Response{status_code: 403, body: err}} ->
|
||||
raise(ArgumentError, message: err)
|
||||
end
|
||||
end
|
||||
|
||||
defp processData(
|
||||
%{
|
||||
"locations" => addresses,
|
||||
"providedLocation" => %{"latLng" => %{"lat" => lat, "lng" => lng}}
|
||||
} = _body
|
||||
) do
|
||||
case addresses do
|
||||
[] -> nil
|
||||
addresses -> addresses |> hd |> produceAddress(lat, lng)
|
||||
end
|
||||
end
|
||||
|
||||
defp processData(%{"locations" => addresses}) do
|
||||
case addresses do
|
||||
[] -> nil
|
||||
addresses -> addresses |> hd |> produceAddress()
|
||||
end
|
||||
end
|
||||
|
||||
defp produceAddress(%{"latLng" => %{"lat" => lat, "lng" => lng}} = address) do
|
||||
produceAddress(address, lat, lng)
|
||||
end
|
||||
|
||||
defp produceAddress(address, lat, lng) do
|
||||
%Address{
|
||||
addressCountry: Map.get(address, "adminArea1"),
|
||||
addressLocality: Map.get(address, "adminArea5"),
|
||||
addressRegion: Map.get(address, "adminArea3"),
|
||||
description: Map.get(address, "street"),
|
||||
floor: Map.get(address, "floor"),
|
||||
geom: [lng, lat] |> Provider.coordinates(),
|
||||
postalCode: Map.get(address, "postalCode"),
|
||||
streetAddress: Map.get(address, "street")
|
||||
}
|
||||
end
|
||||
end
|
||||
89
lib/service/geospatial/nominatim.ex
Normal file
89
lib/service/geospatial/nominatim.ex
Normal file
@@ -0,0 +1,89 @@
|
||||
defmodule Mobilizon.Service.Geospatial.Nominatim do
|
||||
@moduledoc """
|
||||
[Nominatim](https://wiki.openstreetmap.org/wiki/Nominatim) backend.
|
||||
"""
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
alias Mobilizon.Addresses.Address
|
||||
require Logger
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@endpoint Application.get_env(:mobilizon, __MODULE__) |> get_in([:endpoint])
|
||||
@api_key Application.get_env(:mobilizon, __MODULE__) |> get_in([:api_key])
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Nominatim implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
"""
|
||||
@spec geocode(String.t(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
url = build_url(:geocode, %{lon: lon, lat: lat}, options)
|
||||
Logger.debug("Asking Nominatim for geocode with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, body} <- Poison.decode(body) do
|
||||
[process_data(body)]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Nominatim implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
url = build_url(:search, %{q: q}, options)
|
||||
Logger.debug("Asking Nominatim for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, body} <- Poison.decode(body) do
|
||||
Enum.map(body, fn entry -> process_data(entry) end)
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_url(atom(), map(), list()) :: String.t()
|
||||
defp build_url(method, args, options) do
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
lang = Keyword.get(options, :lang, "en")
|
||||
endpoint = Keyword.get(options, :endpoint, @endpoint)
|
||||
api_key = Keyword.get(options, :api_key, @api_key)
|
||||
|
||||
url =
|
||||
case method do
|
||||
:search ->
|
||||
"#{endpoint}/search?format=jsonv2&q=#{URI.encode(args.q)}&limit=#{limit}&accept-language=#{
|
||||
lang
|
||||
}&addressdetails=1"
|
||||
|
||||
:geocode ->
|
||||
"#{endpoint}/reverse?format=jsonv2&lat=#{args.lat}&lon=#{args.lon}&addressdetails=1"
|
||||
end
|
||||
|
||||
if is_nil(api_key), do: url, else: url <> "&key=#{api_key}"
|
||||
end
|
||||
|
||||
@spec process_data(map()) :: Address.t()
|
||||
defp process_data(%{"address" => address} = body) do
|
||||
%Address{
|
||||
addressCountry: Map.get(address, "country"),
|
||||
addressLocality: Map.get(address, "city"),
|
||||
addressRegion: Map.get(address, "state"),
|
||||
description: Map.get(body, "display_name"),
|
||||
floor: Map.get(address, "floor"),
|
||||
geom: [Map.get(body, "lon"), Map.get(body, "lat")] |> Provider.coordinates(),
|
||||
postalCode: Map.get(address, "postcode"),
|
||||
streetAddress: street_address(address)
|
||||
}
|
||||
end
|
||||
|
||||
@spec street_address(map()) :: String.t()
|
||||
defp street_address(body) do
|
||||
if Map.has_key?(body, "house_number") do
|
||||
Map.get(body, "house_number") <> " " <> Map.get(body, "road")
|
||||
else
|
||||
Map.get(body, "road")
|
||||
end
|
||||
end
|
||||
end
|
||||
87
lib/service/geospatial/photon.ex
Normal file
87
lib/service/geospatial/photon.ex
Normal file
@@ -0,0 +1,87 @@
|
||||
defmodule Mobilizon.Service.Geospatial.Photon do
|
||||
@moduledoc """
|
||||
[Photon](https://photon.komoot.de) backend.
|
||||
"""
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
require Logger
|
||||
alias Mobilizon.Addresses.Address
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@endpoint Application.get_env(:mobilizon, __MODULE__) |> get_in([:endpoint])
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Photon implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
|
||||
Note: It seems results are quite wrong.
|
||||
"""
|
||||
@spec geocode(number(), number(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
url = build_url(:geocode, %{lon: lon, lat: lat}, options)
|
||||
Logger.debug("Asking photon for reverse geocoding with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"features" => features}} <- Poison.decode(body) do
|
||||
processData(features)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Photon implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
url = build_url(:search, %{q: q}, options)
|
||||
Logger.debug("Asking photon for addresses with #{url}")
|
||||
|
||||
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
|
||||
HTTPoison.get(url),
|
||||
{:ok, %{"features" => features}} <- Poison.decode(body) do
|
||||
processData(features)
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_url(atom(), map(), list()) :: String.t()
|
||||
defp build_url(method, args, options) do
|
||||
limit = Keyword.get(options, :limit, 10)
|
||||
lang = Keyword.get(options, :lang, "en")
|
||||
coords = Keyword.get(options, :coords, nil)
|
||||
endpoint = Keyword.get(options, :endpoint, @endpoint)
|
||||
|
||||
case method do
|
||||
:search ->
|
||||
url = "#{endpoint}/api/?q=#{URI.encode(args.q)}&lang=#{lang}&limit=#{limit}"
|
||||
if is_nil(coords), do: url, else: url <> "&lat=#{coords.lat}&lon=#{coords.lon}"
|
||||
|
||||
:geocode ->
|
||||
"#{endpoint}/reverse?lon=#{args.lon}&lat=#{args.lat}&lang=#{lang}&limit=#{limit}"
|
||||
end
|
||||
end
|
||||
|
||||
defp processData(features) do
|
||||
features
|
||||
|> Enum.map(fn %{"geometry" => geometry, "properties" => properties} ->
|
||||
%Address{
|
||||
addressCountry: Map.get(properties, "country"),
|
||||
addressLocality: Map.get(properties, "city"),
|
||||
addressRegion: Map.get(properties, "state"),
|
||||
description: Map.get(properties, "name") || streetAddress(properties),
|
||||
floor: Map.get(properties, "floor"),
|
||||
geom: Map.get(geometry, "coordinates") |> Provider.coordinates(),
|
||||
postalCode: Map.get(properties, "postcode"),
|
||||
streetAddress: properties |> streetAddress()
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp streetAddress(properties) do
|
||||
if Map.has_key?(properties, "housenumber") do
|
||||
Map.get(properties, "housenumber") <> " " <> Map.get(properties, "street")
|
||||
else
|
||||
Map.get(properties, "street")
|
||||
end
|
||||
end
|
||||
end
|
||||
72
lib/service/geospatial/provider.ex
Normal file
72
lib/service/geospatial/provider.ex
Normal file
@@ -0,0 +1,72 @@
|
||||
defmodule Mobilizon.Service.Geospatial.Provider do
|
||||
@moduledoc """
|
||||
Provider Behaviour for Geospatial stuff.
|
||||
|
||||
## Supported backends
|
||||
|
||||
* `Mobilizon.Service.Geospatial.Nominatim` [🔗](https://wiki.openstreetmap.org/wiki/Nominatim)
|
||||
* `Mobilizon.Service.Geospatial.Photon` [🔗](https://photon.komoot.de)
|
||||
* `Mobilizon.Service.Geospatial.Addok` [🔗](https://github.com/addok/addok)
|
||||
* `Mobilizon.Service.Geospatial.MapQuest` [🔗](https://developer.mapquest.com/documentation/open/)
|
||||
* `Mobilizon.Service.Geospatial.GoogleMaps` [🔗](https://developers.google.com/maps/documentation/geocoding/intro)
|
||||
|
||||
|
||||
## Shared options
|
||||
|
||||
* `:user_agent` User-Agent string to send to the backend. Defaults to `"Mobilizon"`
|
||||
* `:lang` Lang in which to prefer results. Used as a request parameter or through an `Accept-Language` HTTP header.
|
||||
Defaults to `"en"`.
|
||||
* `:limit` Maximum limit for the number of results returned by the backend. Defaults to `10`
|
||||
* `:api_key` Allows to override the API key (if the backend requires one) set inside the configuration.
|
||||
* `:endpoint` Allows to override the endpoint set inside the configuration
|
||||
"""
|
||||
|
||||
alias Mobilizon.Addresses.Address
|
||||
|
||||
@doc """
|
||||
Get an address from longitude and latitude coordinates.
|
||||
|
||||
## Options
|
||||
|
||||
Most backends implement all of [the shared options](#module-shared-options).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> geocode(48.11, -1.77)
|
||||
%Address{}
|
||||
"""
|
||||
@callback geocode(longitude :: number(), latitude :: number(), options :: keyword()) ::
|
||||
list(Address.t())
|
||||
|
||||
@doc """
|
||||
Search for an address
|
||||
|
||||
## Options
|
||||
|
||||
In addition to [the shared options](#module-shared-options), `c:search/2` also accepts the following options:
|
||||
|
||||
* `coords` Map of coordinates (ex: `%{lon: 48.11, lat: -1.77}`) allowing to give a geographic priority to the search.
|
||||
Defaults to `nil`
|
||||
|
||||
## Examples
|
||||
|
||||
iex> search("10 rue Jangot")
|
||||
%Address{}
|
||||
"""
|
||||
@callback search(address :: String.t(), options :: keyword()) :: list(Address.t())
|
||||
|
||||
@doc """
|
||||
Returns a `Geo.Point` for given coordinates
|
||||
"""
|
||||
@spec coordinates(list(number()), number()) :: Geo.Point.t()
|
||||
def coordinates(coords, srid \\ 4326)
|
||||
|
||||
def coordinates([x, y], srid) when is_number(x) and is_number(y),
|
||||
do: %Geo.Point{coordinates: {x, y}, srid: srid}
|
||||
|
||||
def coordinates([x, y], srid) when is_bitstring(x) and is_bitstring(y),
|
||||
do: %Geo.Point{coordinates: {String.to_float(x), String.to_float(y)}, srid: srid}
|
||||
|
||||
@spec coordinates(any()) :: nil
|
||||
def coordinates(_, _), do: nil
|
||||
end
|
||||
Reference in New Issue
Block a user