Introduce support for Pelias geocoder

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-11-19 17:59:04 +01:00
parent 792a2deddb
commit c7b25474d3
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
11 changed files with 139 additions and 13 deletions

View file

@ -140,6 +140,9 @@ config :mobilizon, Mobilizon.Service.Geospatial.MapQuest,
config :mobilizon, Mobilizon.Service.Geospatial.Mimirsbrunn,
endpoint: System.get_env("GEOSPATIAL_MIMIRSBRUNN_ENDPOINT") || nil
config :mobilizon, Mobilizon.Service.Geospatial.Pelias,
endpoint: System.get_env("GEOSPATIAL_PELIAS_ENDPOINT") || nil
config :mobilizon, Oban,
repo: Mobilizon.Storage.Repo,
prune: {:maxlen, 10_000},

View file

@ -25,7 +25,8 @@
<template slot="empty">
<span v-if="isFetching">{{ $t('Searching') }}</span>
<div v-else class="is-enabled">
<span>{{ $t('No results for "{queryText}". You can try another search term or drag and drop the marker on the map', { queryText }) }}</span>
<span>{{ $t('No results for "{queryText}"') }}</span>
<span>{{ $t('You can try another search term or drag and drop the marker on the map', { queryText }) }}</span>
<!-- <p class="control" @click="openNewAddressModal">-->
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
<!-- </p>-->

View file

@ -71,7 +71,6 @@
"Didn't receive the instructions ?": "Bestätigung nicht erhalten?",
"Display name": "Namen einzeigen",
"Display participation price": "Teilnahmegebühr anzeigen",
"Displayed name": "Angezeigter Name",
"Draft": "Entwurf",
"Drafts": "Entwürfe",
"Edit": "Bearbeiten",
@ -162,7 +161,6 @@
"No group found": "Keine Gruppe gefunden",
"No groups found": "Keine Gruppen gefunden",
"No results for \"{queryText}\"": "Keine Ergebnisse für \"{queryText}\"",
"No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "Keine Ergebinsse für \"{queryText}\". Du kannst es erneut mit anderen Begriffen versuchen, oder mit Drag&Drop den Marker auf die Karte setzen",
"No user account with this email was found. Maybe you made a typo?": "Kein Account mit dieser E-Mail gefunden. Vielleicht hast Du dich vertippt?",
"Number of places": "Anzahl der Plätze",
"OK": "OK",

View file

@ -71,7 +71,6 @@
"Didn't receive the instructions ?": "Didn't receive the instructions ?",
"Display name": "Display name",
"Display participation price": "Display participation price",
"Displayed name": "Displayed name",
"Draft": "Draft",
"Drafts": "Drafts",
"Edit": "Edit",
@ -161,7 +160,7 @@
"No events found": "No events found",
"No group found": "No group found",
"No groups found": "No groups found",
"No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map",
"No results for \"{queryText}\"": "No results for \"{queryText}\"",
"No user account with this email was found. Maybe you made a typo?": "No user account with this email was found. Maybe you made a typo?",
"Number of places": "Number of places",
"OK": "OK",
@ -172,6 +171,7 @@
"On {date}": "On {date}",
"One person is going": "No one is going | One person is going | {approved} persons are going",
"Only accessible through link and search (private)": "Only accessible through link and search (private)",
"Only alphanumeric characters and underscores are supported.": "Only alphanumeric characters and underscores are supported.",
"Opened reports": "Opened reports",
"Organized by {name}": "Organized by {name}",
"Organized": "Organized",
@ -298,6 +298,7 @@
"You are already a participant of this event.": "You are already a participant of this event.",
"You are already logged-in.": "You are already logged-in.",
"You can add tags by hitting the Enter key or by adding a comma": "You can add tags by hitting the Enter key or by adding a comma",
"You can try another search term or drag and drop the marker on the map": "You can try another search term or drag and drop the marker on the map",
"You can't remove your last identity.": "You can't remove your last identity.",
"You have been disconnected": "You have been disconnected",
"You have cancelled your participation": "You have cancelled your participation",

View file

@ -71,7 +71,6 @@
"Didn't receive the instructions ?": "Vous n'avez pas reçu les instructions ?",
"Display name": "Nom affiché",
"Display participation price": "Afficher un prix de participation",
"Displayed name": "Nom affiché",
"Draft": "Brouillon",
"Drafts": "Brouillons",
"Edit": "Éditer",
@ -162,7 +161,6 @@
"No group found": "Aucun groupe trouvé",
"No groups found": "Aucun groupe trouvé",
"No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »",
"No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "Pas de résultats pour « {queryText} ». Vous pouvez essayer avec d'autres termes de recherche ou bien glisser et déposer le marqueur sur la carte",
"No user account with this email was found. Maybe you made a typo?": "Aucun compte utilisateur trouvé pour cet email. Peut-être avez-vous fait une faute de frappe ?",
"Number of places": "Nombre de places",
"OK": "OK",
@ -173,6 +171,7 @@
"On {date}": "Le {date}",
"One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont",
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)",
"Only alphanumeric characters and underscores are supported.": "Seuls les caractères alphanumériques et les tirets bas sont acceptés.",
"Opened reports": "Signalements ouverts",
"Organized by {name}": "Organisé par {name}",
"Organized": "Organisés",
@ -300,6 +299,7 @@
"You are already a participant of this event.": "Vous participez déjà à cet événement.",
"You are already logged-in.": "Vous êtes déjà connecté.",
"You can add tags by hitting the Enter key or by adding a comma": "Vous pouvez ajouter des tags en appuyant sur la touche Entrée ou bien en ajoutant une virgule",
"You can try another search term or drag and drop the marker on the map": "Vous pouvez essayer avec d'autres termes de recherche ou bien glisser et déposer le marqueur sur la carte",
"You can't remove your last identity.": "Vous ne pouvez pas supprimer votre dernière identité.",
"You have been disconnected": "Vous avez été déconnecté⋅e",
"You have cancelled your participation": "Vous avez annulé votre participation",

View file

@ -71,7 +71,6 @@
"Didn't receive the instructions ?": "Hebt u de instructies niet ontvangen?",
"Display name": "Getoonde naam",
"Display participation price": "Prijs voor deelname tonen",
"Displayed name": "Getoonde naam",
"Draft": "Concept",
"Drafts": "Concepten",
"Edit": "Bewerken",

View file

@ -76,7 +76,6 @@
"Disallow promoting on Mobilizon": "Refusar la promocion sus Mobilizon",
"Display name": "Nom mostrat",
"Display participation price": "Far veire un prètz de participacion",
"Displayed name": "Nom mostrat",
"Do you want to participate in {title}?": "Volètz participar a {title} ?",
"Draft": "Borrolhon",
"Drafts": "Borrolhons",
@ -184,7 +183,6 @@
"No groups found": "Cap de grop pas trobat",
"No participants yet": "Cap de participant pel moment",
"No results for \"{queryText}\"": "Cap de resultats per « {queryText} »",
"No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "Cap de resultat per « {queryText} ». Podètz ensajar un autre tèrme de recèrca o botar lo marcador sus la mapa",
"No user account with this email was found. Maybe you made a typo?": "Pas de compte utilizaire pas trobat amb aquesta adreça. Benlèu quavètz fach una deca ?",
"Number of places": "Nombre de plaças",
"OK": "OK",

View file

@ -69,7 +69,6 @@
"Didn't receive the instructions ?": "Nie otrzymałeś(-aś) instrukcji?",
"Display name": "Wyświetlana nazwa",
"Display participation price": "Wyświetlaj cenę udziału",
"Displayed name": "Wyświetlana nazwa",
"Draft": "Szkic",
"Drafts": "Szkice",
"Edit": "Edytuj",

View file

@ -71,7 +71,6 @@
"Didn't receive the instructions ?": "Fick inte instruktionerna?",
"Display name": "Visa namn",
"Display participation price": "Visa pris för deltagande",
"Displayed name": "Visat namn",
"Draft": "Utkast",
"Drafts": "Utkast",
"Edit": "Redigera",

View file

@ -81,8 +81,10 @@ export class Address implements IAddress {
}
} else if (this.locality && this.locality.trim()) {
alternativeName = `${this.locality}, ${this.region}, ${this.country}`;
} else {
} else if (this.region && this.region.trim()) {
alternativeName = `${this.region}, ${this.country}`;
} else if (this.country && this.country.trim()) {
alternativeName = this.country;
}
poiIcon = this.iconForPOI;
break;

View file

@ -0,0 +1,126 @@
defmodule Mobilizon.Service.Geospatial.Pelias do
@moduledoc """
[Pelias](https://pelias.io) backend.
"""
alias Mobilizon.Addresses.Address
alias Mobilizon.Service.Geospatial.Provider
alias Mobilizon.Config
require Logger
@behaviour Provider
@endpoint Application.get_env(:mobilizon, __MODULE__) |> get_in([:endpoint])
@impl Provider
@doc """
Pelias implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
"""
@spec geocode(number(), number(), keyword()) :: list(Address.t())
def geocode(lon, lat, options \\ []) do
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent())
headers = [{"User-Agent", user_agent}]
url = build_url(:geocode, %{lon: lon, lat: lat}, options)
Logger.debug("Asking Pelias for reverse geocoding with #{url}")
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
HTTPoison.get(url, headers),
{:ok, %{"features" => features}} <- Poison.decode(body) do
process_data(features)
end
end
@impl Provider
@doc """
Pelias implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
"""
@spec search(String.t(), keyword()) :: list(Address.t())
def search(q, options \\ []) do
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent())
headers = [{"User-Agent", user_agent}]
url = build_url(:search, %{q: q}, options)
Logger.debug("Asking Pelias for addresses with #{url}")
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
HTTPoison.get(url, headers),
{:ok, %{"features" => features}} <- Poison.decode(body) do
process_data(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)
country_code = Keyword.get(options, :country_code)
url =
case method do
:search ->
url =
"#{endpoint}/v1/autocomplete?text=#{URI.encode(args.q)}&lang=#{lang}&size=#{limit}"
if is_nil(coords),
do: url,
else: url <> "&focus.point.lat=#{coords.lat}&focus.point.lon=#{coords.lon}"
:geocode ->
"#{endpoint}/v1/reverse?point.lon=#{args.lon}&point.lat=#{args.lat}"
end
if is_nil(country_code), do: url, else: "#{url}&boundary.country=#{country_code}"
end
defp process_data(features) do
features
|> Enum.map(fn %{
"geometry" => %{"coordinates" => coordinates},
"properties" => properties
} ->
address = process_address(properties)
%Address{address | geom: Provider.coordinates(coordinates)}
end)
end
defp process_address(properties) do
%Address{
country: Map.get(properties, "country"),
locality: Map.get(properties, "locality"),
region: Map.get(properties, "region"),
description: Map.get(properties, "name"),
postal_code: Map.get(properties, "postalcode"),
street: street_address(properties),
origin_id: "pelias:#{Map.get(properties, "id")}",
type: get_type(properties)
}
end
defp street_address(properties) do
if Map.has_key?(properties, "housenumber") do
"#{Map.get(properties, "housenumber")} #{Map.get(properties, "street")}"
else
Map.get(properties, "street")
end
end
@administrative_layers [
"neighbourhood",
"borough",
"localadmin",
"locality",
"county",
"macrocounty",
"region",
"macroregion",
"dependency"
]
defp get_type(%{"layer" => layer}) when layer in @administrative_layers, do: "administrative"
defp get_type(%{"layer" => "address"}), do: "house"
defp get_type(%{"layer" => "street"}), do: "street"
defp get_type(%{"layer" => "venue"}), do: "venue"
defp get_type(%{"layer" => _}), do: nil
end