2024-04-29 00:17:15 +02:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2022-12-20 21:27:52 +01:00
|
|
|
|
class APIGeoService
|
|
|
|
|
class << self
|
|
|
|
|
def countries(locale: I18n.locale)
|
|
|
|
|
I18nData.countries(locale)
|
|
|
|
|
.merge(get_localized_additional_countries(locale))
|
|
|
|
|
.map { |(code, name)| { name:, code: } }
|
2022-12-22 17:24:08 +01:00
|
|
|
|
.sort_by { I18n.transliterate(_1[:name].tr('î', 'Î')) }
|
2022-12-20 21:27:52 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def country_name(code, locale: I18n.locale)
|
|
|
|
|
countries(locale:).find { _1[:code] == code }&.dig(:name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def country_code(name)
|
|
|
|
|
return if name.nil?
|
|
|
|
|
code = I18nData.country_code(name) || I18nData.country_code(name.humanize) || I18nData.country_code(name.titleize)
|
|
|
|
|
if code.nil?
|
|
|
|
|
countries_index_fr[I18n.transliterate(name).upcase]&.dig(:code)
|
|
|
|
|
else
|
|
|
|
|
code
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def regions
|
|
|
|
|
get_from_api_geo(:regions).sort_by { I18n.transliterate(_1[:name]) }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def region_name(code)
|
|
|
|
|
regions.find { _1[:code] == code }&.dig(:name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def region_code(name)
|
|
|
|
|
return if name.nil?
|
|
|
|
|
regions.find { _1[:name] == name }&.dig(:code)
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-13 13:40:33 +01:00
|
|
|
|
def region_code_by_departement(code)
|
|
|
|
|
return if code.nil?
|
|
|
|
|
departements.find { _1[:code] == code }&.dig(:region_code)
|
2023-11-24 15:51:17 +01:00
|
|
|
|
end
|
|
|
|
|
|
2022-12-20 21:27:52 +01:00
|
|
|
|
def departements
|
2023-04-13 13:09:56 +02:00
|
|
|
|
[{ code: '99', name: 'Etranger' }] + get_from_api_geo(:departements).sort_by { _1[:code] }
|
2022-12-20 21:27:52 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def departement_name(code)
|
|
|
|
|
departements.find { _1[:code] == code }&.dig(:name)
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-21 11:01:40 +02:00
|
|
|
|
def departement_name_by_postal_code(postal_code)
|
|
|
|
|
APIGeoService.departement_name(postal_code[0..2]) || APIGeoService.departement_name(postal_code[0..1])
|
|
|
|
|
end
|
|
|
|
|
|
2022-12-20 21:27:52 +01:00
|
|
|
|
def departement_code(name)
|
|
|
|
|
return if name.nil?
|
|
|
|
|
departements.find { _1[:name] == name }&.dig(:code)
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-19 09:43:19 +01:00
|
|
|
|
def epcis(departement_code)
|
2023-04-13 13:09:56 +02:00
|
|
|
|
get_from_api_geo("epcis-#{departement_code}").sort_by { I18n.transliterate(_1[:name]) }
|
2023-01-19 09:43:19 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def epci_name(departement_code, code)
|
|
|
|
|
epcis(departement_code).find { _1[:code] == code }&.dig(:name)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def epci_code(departement_code, name)
|
|
|
|
|
epcis(departement_code).find { _1[:name] == name }&.dig(:code)
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-21 14:42:59 +01:00
|
|
|
|
def communes(departement_code)
|
2023-04-13 13:09:56 +02:00
|
|
|
|
get_from_api_geo("communes-#{departement_code}").sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) }
|
2023-03-21 14:42:59 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def communes_by_postal_code(postal_code)
|
2023-04-13 13:09:56 +02:00
|
|
|
|
communes_by_postal_code_map.fetch(postal_code, [])
|
2024-03-15 22:33:26 +01:00
|
|
|
|
.filter { !_1[:code].in?(['75056', '13055', '69123']) }
|
2023-04-13 13:09:56 +02:00
|
|
|
|
.sort_by { I18n.transliterate([_1[:name], _1[:postal_code]].join(' ')) }
|
2023-03-21 14:42:59 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def commune_name(departement_code, code)
|
|
|
|
|
communes(departement_code).find { _1[:code] == code }&.dig(:name)
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-05 17:50:32 +02:00
|
|
|
|
def commune_by_name_or_postal_code(query)
|
|
|
|
|
if postal_code?(query)
|
|
|
|
|
fetch_by_postal_code(query)
|
|
|
|
|
else
|
|
|
|
|
fetch_by_name(query)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-21 14:42:59 +01:00
|
|
|
|
def commune_code(departement_code, name)
|
|
|
|
|
communes(departement_code).find { _1[:name] == name }&.dig(:code)
|
|
|
|
|
end
|
|
|
|
|
|
2024-03-01 16:46:59 +01:00
|
|
|
|
def commune_postal_codes(departement_code, code)
|
|
|
|
|
communes(departement_code).filter { _1[:code] == code }.map { _1[:postal_code] }
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-13 13:40:33 +01:00
|
|
|
|
def parse_ban_address(feature)
|
|
|
|
|
return unless ban_address_schema.valid?(feature)
|
|
|
|
|
|
|
|
|
|
properties = feature.fetch('properties')
|
|
|
|
|
city_code = properties.fetch('citycode')
|
|
|
|
|
|
|
|
|
|
territory = if properties['context'].present?
|
|
|
|
|
department_code = properties.fetch('context').split(',').first
|
|
|
|
|
region_code = region_code_by_departement(department_code)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
department_name: departement_name(department_code),
|
|
|
|
|
department_code:,
|
|
|
|
|
region_name: region_name(region_code),
|
|
|
|
|
region_code:,
|
2024-04-10 10:47:21 +02:00
|
|
|
|
city_name: safely_normalize_city_name(department_code, city_code, properties['city']),
|
2024-02-13 13:40:33 +01:00
|
|
|
|
city_code:
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
city_name: properties['city'],
|
|
|
|
|
city_code:
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
label: properties.fetch('label'),
|
|
|
|
|
type: properties.fetch('type'),
|
|
|
|
|
street_address: properties.fetch('name'),
|
2024-04-16 14:54:53 +02:00
|
|
|
|
postal_code: properties.fetch('postcode') { '' }, # API graphql / serializer requires non-null data
|
2024-02-13 13:40:33 +01:00
|
|
|
|
street_number: properties['housenumber'],
|
|
|
|
|
street_name: properties['street'],
|
|
|
|
|
geometry: feature['geometry']
|
|
|
|
|
}.merge(territory)
|
|
|
|
|
end
|
|
|
|
|
|
2024-07-24 10:23:00 +02:00
|
|
|
|
def parse_rna_address(address)
|
|
|
|
|
postal_code = address[:code_postal]
|
|
|
|
|
city_name_fallback = address[:commune]
|
|
|
|
|
city_code = address[:code_insee]
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_code, region_code = if postal_code.present? && city_code.present?
|
2024-07-24 10:23:00 +02:00
|
|
|
|
commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code }
|
|
|
|
|
if commune.present?
|
|
|
|
|
[commune[:departement_code], commune[:region_code]]
|
|
|
|
|
else
|
|
|
|
|
[]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_name = departement_name(department_code)
|
2024-07-24 10:23:00 +02:00
|
|
|
|
{
|
|
|
|
|
street_number: address[:numero_voie],
|
|
|
|
|
street_name: address[:libelle_voie],
|
|
|
|
|
street_address: address[:libelle_voie].present? ? [address[:numero_voie], address[:type_voie], address[:libelle_voie]].compact.join(' ') : nil,
|
|
|
|
|
postal_code: postal_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback),
|
2024-07-24 10:23:00 +02:00
|
|
|
|
city_code: city_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
departement_code: department_code,
|
|
|
|
|
department_code:,
|
|
|
|
|
departement_name: department_name,
|
|
|
|
|
department_name:,
|
2024-07-24 10:23:00 +02:00
|
|
|
|
region_code:,
|
|
|
|
|
region_name: region_name(region_code)
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def parse_rnf_address(address)
|
|
|
|
|
postal_code = address[:postalCode]
|
|
|
|
|
city_name_fallback = address[:cityName]
|
|
|
|
|
city_code = address[:cityCode]
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_code, region_code = if postal_code.present? && city_code.present?
|
2024-07-24 10:23:00 +02:00
|
|
|
|
commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code }
|
|
|
|
|
if commune.present?
|
|
|
|
|
[commune[:departement_code], commune[:region_code]]
|
|
|
|
|
else
|
|
|
|
|
[]
|
|
|
|
|
end
|
|
|
|
|
end
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_name = departement_name(department_code)
|
2024-07-24 10:23:00 +02:00
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
street_number: address[:streetNumber],
|
|
|
|
|
street_name: address[:streetName],
|
|
|
|
|
street_address: address[:streetAddress],
|
|
|
|
|
postal_code: postal_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback),
|
2024-07-24 10:23:00 +02:00
|
|
|
|
city_code: city_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
departement_code: department_code,
|
|
|
|
|
department_code:,
|
|
|
|
|
departement_name: department_name,
|
|
|
|
|
department_name:,
|
2024-07-24 10:23:00 +02:00
|
|
|
|
region_code:,
|
|
|
|
|
region_name: region_name(region_code)
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def parse_etablissement_address(etablissement)
|
|
|
|
|
postal_code = etablissement.code_postal
|
|
|
|
|
city_name_fallback = etablissement.localite.presence || ''
|
|
|
|
|
city_code = etablissement.code_insee_localite
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_code, region_code = if postal_code.present? && city_code.present?
|
2024-07-24 10:23:00 +02:00
|
|
|
|
commune = communes_by_postal_code(postal_code).find { _1[:code] == city_code }
|
|
|
|
|
if commune.present?
|
|
|
|
|
[commune[:departement_code], commune[:region_code]]
|
|
|
|
|
else
|
|
|
|
|
[]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-10-14 09:54:17 +02:00
|
|
|
|
department_name = departement_name(department_code)
|
|
|
|
|
|
2024-07-24 10:23:00 +02:00
|
|
|
|
{
|
|
|
|
|
street_number: etablissement.numero_voie,
|
|
|
|
|
street_name: etablissement.nom_voie,
|
|
|
|
|
street_address: etablissement.nom_voie.present? ? [etablissement.numero_voie, etablissement.type_voie, etablissement.nom_voie].compact.join(' ') : nil,
|
|
|
|
|
postal_code: postal_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
city_name: safely_normalize_city_name(department_code, city_code, city_name_fallback),
|
2024-07-24 10:23:00 +02:00
|
|
|
|
city_code: city_code.presence || '',
|
2024-10-14 09:54:17 +02:00
|
|
|
|
departement_code: department_code,
|
|
|
|
|
department_code:,
|
|
|
|
|
departement_name: department_name,
|
|
|
|
|
department_name:,
|
2024-07-24 10:23:00 +02:00
|
|
|
|
region_code:,
|
|
|
|
|
region_name: region_name(region_code)
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
2024-04-10 10:47:21 +02:00
|
|
|
|
def safely_normalize_city_name(department_code, city_code, fallback)
|
2024-04-15 11:01:15 +02:00
|
|
|
|
return fallback if department_code.blank? || city_code.blank?
|
2024-04-10 10:47:21 +02:00
|
|
|
|
|
|
|
|
|
commune_name(department_code, city_code) || fallback
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-05 17:50:32 +02:00
|
|
|
|
def format_commune_response(results, with_combined_code)
|
|
|
|
|
results.reject(&method(:code_metropole?)).flat_map do |result|
|
|
|
|
|
item = {
|
|
|
|
|
name: result[:nom].tr("'", '’'),
|
|
|
|
|
code: result[:code],
|
|
|
|
|
epci_code: result[:codeEpci],
|
|
|
|
|
departement_code: result[:codeDepartement]
|
|
|
|
|
}.compact
|
|
|
|
|
|
|
|
|
|
if result[:codesPostaux].present?
|
|
|
|
|
result[:codesPostaux].map { item.merge(postal_code: _1) }
|
|
|
|
|
else
|
|
|
|
|
[item]
|
|
|
|
|
end.map do |item|
|
|
|
|
|
if with_combined_code.present?
|
|
|
|
|
{
|
|
|
|
|
label: "#{item[:name]} (#{item[:postal_code]})",
|
|
|
|
|
value: "#{item[:code]}-#{item[:postal_code]}"
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
label: "#{item[:name]} (#{item[:postal_code]})",
|
|
|
|
|
value: item[:code],
|
|
|
|
|
data: item[:postal_code]
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-12-20 21:27:52 +01:00
|
|
|
|
private
|
|
|
|
|
|
2024-09-05 17:50:32 +02:00
|
|
|
|
def code_metropole?(result)
|
|
|
|
|
result[:code].in?(['75056', '13055', '69123'])
|
|
|
|
|
end
|
|
|
|
|
|
2023-04-13 13:09:56 +02:00
|
|
|
|
def communes_by_postal_code_map
|
2023-07-25 12:43:13 +02:00
|
|
|
|
Rails.cache.fetch('api_geo_communes', expires_in: 1.day, version: 3) do
|
2023-04-13 13:09:56 +02:00
|
|
|
|
departements
|
|
|
|
|
.filter { _1[:code] != '99' }
|
|
|
|
|
.flat_map { communes(_1[:code]) }
|
|
|
|
|
.group_by { _1[:postal_code] }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-03-21 14:42:59 +01:00
|
|
|
|
def get_from_api_geo(scope)
|
2023-07-25 12:43:13 +02:00
|
|
|
|
Rails.cache.fetch("api_geo_#{scope}", expires_in: 1.day, version: 3) do
|
2023-04-13 13:09:56 +02:00
|
|
|
|
JSON.parse(Rails.root.join('lib', 'data', 'api_geo', "#{scope}.json").read, symbolize_names: true)
|
2022-12-20 21:27:52 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def countries_index_fr
|
2023-04-13 13:09:56 +02:00
|
|
|
|
Rails.cache.fetch('countries_index_fr', expires_in: 1.week) do
|
2022-12-20 21:27:52 +01:00
|
|
|
|
countries(locale: 'FR').index_by { I18n.transliterate(_1[:name]).upcase }
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def get_localized_additional_countries(locale)
|
|
|
|
|
additional_countries[locale.to_s.upcase] || {}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def additional_countries
|
|
|
|
|
{
|
|
|
|
|
'FR' => { 'XK' => 'Kosovo' },
|
|
|
|
|
'EN' => { 'XK' => 'Kosovo' }
|
|
|
|
|
}
|
|
|
|
|
end
|
2024-02-13 13:40:33 +01:00
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
2024-09-05 17:50:32 +02:00
|
|
|
|
def fetch_by_name(name)
|
|
|
|
|
Typhoeus.get("#{API_GEO_URL}/communes", params: {
|
|
|
|
|
type: 'commune-actuelle,arrondissement-municipal',
|
|
|
|
|
nom: name,
|
|
|
|
|
boost: 'population',
|
|
|
|
|
limit: 100
|
|
|
|
|
}, timeout: 3)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def fetch_by_postal_code(postal_code)
|
|
|
|
|
Typhoeus.get("#{API_GEO_URL}/communes", params: {
|
|
|
|
|
type: 'commune-actuelle,arrondissement-municipal',
|
|
|
|
|
codePostal: postal_code,
|
|
|
|
|
boost: 'population',
|
|
|
|
|
limit: 50
|
|
|
|
|
}, timeout: 3)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def postal_code?(string)
|
|
|
|
|
string.match?(/\A[-+]?\d+\z/) ? true : false
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-13 13:40:33 +01:00
|
|
|
|
def ban_address_schema
|
|
|
|
|
JSONSchemer.schema(Rails.root.join('app/schemas/adresse-ban.json'))
|
|
|
|
|
end
|
2022-12-20 21:27:52 +01:00
|
|
|
|
end
|
|
|
|
|
end
|