The commit https://github.com/odoo/odoo/commit/160e8bfbf72a3e5d7cc8d8cbe7bc4f310f298baa introduced a way to update the useragent used to request openstreetmap nominatim service. It was introduced as a bad fix to prevent an erroneous blacklist from the service. Once the blacklist has been removed, this fix was not necessary anymore. It also allows to abuse the free service which is against the osmfoundation policy. The best solution would be to decrease the amount of request. The Google Place Map service is also available to avoid using the openstreetmap service with large database. closes odoo/odoo#138078 X-original-commit: 2726d2724e06e5818eaf2d0010971554372c4727 Signed-off-by: Arnaud Joset (arj) <arj@odoo.com>
161 lines
6.6 KiB
Python
161 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import requests
|
|
import logging
|
|
|
|
from odoo import api, fields, models, tools, _
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GeoProvider(models.Model):
|
|
_name = "base.geo_provider"
|
|
_description = "Geo Provider"
|
|
|
|
tech_name = fields.Char(string="Technical Name")
|
|
name = fields.Char()
|
|
|
|
|
|
class GeoCoder(models.AbstractModel):
|
|
"""
|
|
Abstract class used to call Geolocalization API and convert addresses
|
|
into GPS coordinates.
|
|
"""
|
|
_name = "base.geocoder"
|
|
_description = "Geo Coder"
|
|
|
|
@api.model
|
|
def _get_provider(self):
|
|
prov_id = self.env['ir.config_parameter'].sudo().get_param('base_geolocalize.geo_provider')
|
|
if prov_id:
|
|
provider = self.env['base.geo_provider'].browse(int(prov_id))
|
|
if not prov_id or not provider.exists():
|
|
provider = self.env['base.geo_provider'].search([], limit=1)
|
|
return provider
|
|
|
|
@api.model
|
|
def geo_query_address(self, street=None, zip=None, city=None, state=None, country=None):
|
|
""" Converts address fields into a valid string for querying
|
|
geolocation APIs.
|
|
:param street: street address
|
|
:param zip: zip code
|
|
:param city: city
|
|
:param state: state
|
|
:param country: country
|
|
:return: formatted string
|
|
"""
|
|
provider = self._get_provider().tech_name
|
|
if hasattr(self, '_geo_query_address_' + provider):
|
|
# Makes the transformation defined for provider
|
|
return getattr(self, '_geo_query_address_' + provider)(street, zip, city, state, country)
|
|
else:
|
|
# By default, join the non-empty parameters
|
|
return self._geo_query_address_default(street=street, zip=zip, city=city, state=state, country=country)
|
|
|
|
@api.model
|
|
def geo_find(self, addr, **kw):
|
|
"""Use a location provider API to convert an address string into a latitude, longitude tuple.
|
|
Here we use Openstreetmap Nominatim by default.
|
|
:param addr: Address string passed to API
|
|
:return: (latitude, longitude) or None if not found
|
|
"""
|
|
provider = self._get_provider().tech_name
|
|
try:
|
|
service = getattr(self, '_call_' + provider)
|
|
result = service(addr, **kw)
|
|
except AttributeError:
|
|
raise UserError(_(
|
|
'Provider %s is not implemented for geolocation service.'
|
|
) % provider)
|
|
except UserError:
|
|
raise
|
|
except Exception:
|
|
_logger.debug('Geolocalize call failed', exc_info=True)
|
|
result = None
|
|
return result
|
|
|
|
@api.model
|
|
def _call_openstreetmap(self, addr, **kw):
|
|
"""
|
|
Use Openstreemap Nominatim service to retrieve location
|
|
:return: (latitude, longitude) or None if not found
|
|
"""
|
|
if not addr:
|
|
_logger.info('invalid address given')
|
|
return None
|
|
url = 'https://nominatim.openstreetmap.org/search'
|
|
try:
|
|
headers = {'User-Agent': 'Odoo (http://www.odoo.com/contactus)'}
|
|
response = requests.get(url, headers=headers, params={'format': 'json', 'q': addr})
|
|
_logger.info('openstreetmap nominatim service called')
|
|
if response.status_code != 200:
|
|
_logger.warning('Request to openstreetmap failed.\nCode: %s\nContent: %s', response.status_code, response.content)
|
|
result = response.json()
|
|
except Exception as e:
|
|
self._raise_query_error(e)
|
|
geo = result[0]
|
|
return float(geo['lat']), float(geo['lon'])
|
|
|
|
@api.model
|
|
def _call_googlemap(self, addr, **kw):
|
|
""" Use google maps API. It won't work without a valid API key.
|
|
:return: (latitude, longitude) or None if not found
|
|
"""
|
|
apikey = self.env['ir.config_parameter'].sudo().get_param('base_geolocalize.google_map_api_key')
|
|
if not apikey:
|
|
raise UserError(_(
|
|
"API key for GeoCoding (Places) required.\n"
|
|
"Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information."
|
|
))
|
|
url = "https://maps.googleapis.com/maps/api/geocode/json"
|
|
params = {'sensor': 'false', 'address': addr, 'key': apikey}
|
|
if kw.get('force_country'):
|
|
params['components'] = 'country:%s' % kw['force_country']
|
|
try:
|
|
result = requests.get(url, params).json()
|
|
except Exception as e:
|
|
self._raise_query_error(e)
|
|
|
|
try:
|
|
if result['status'] == 'ZERO_RESULTS':
|
|
return None
|
|
if result['status'] != 'OK':
|
|
_logger.debug('Invalid Gmaps call: %s - %s',
|
|
result['status'], result.get('error_message', ''))
|
|
error_msg = _('Unable to geolocate, received the error:\n%s'
|
|
'\n\nGoogle made this a paid feature.\n'
|
|
'You should first enable billing on your Google account.\n'
|
|
'Then, go to Developer Console, and enable the APIs:\n'
|
|
'Geocoding, Maps Static, Maps Javascript.\n') % result.get('error_message')
|
|
raise UserError(error_msg)
|
|
geo = result['results'][0]['geometry']['location']
|
|
return float(geo['lat']), float(geo['lng'])
|
|
except (KeyError, ValueError):
|
|
_logger.debug('Unexpected Gmaps API answer %s', result.get('error_message', ''))
|
|
return None
|
|
|
|
@api.model
|
|
def _geo_query_address_default(self, street=None, zip=None, city=None, state=None, country=None):
|
|
address_list = [
|
|
street,
|
|
("%s %s" % (zip or '', city or '')).strip(),
|
|
state,
|
|
country
|
|
]
|
|
address_list = [item for item in address_list if item]
|
|
return tools.ustr(', '.join(address_list))
|
|
|
|
@api.model
|
|
def _geo_query_address_googlemap(self, street=None, zip=None, city=None, state=None, country=None):
|
|
# put country qualifier in front, otherwise GMap gives wrong# results
|
|
# e.g. 'Congo, Democratic Republic of the' => 'Democratic Republic of the Congo'
|
|
if country and ',' in country and (
|
|
country.endswith(' of') or country.endswith(' of the')):
|
|
country = '{1} {0}'.format(*country.split(',', 1))
|
|
return self._geo_query_address_default(street=street, zip=zip, city=city, state=state, country=country)
|
|
|
|
def _raise_query_error(self, error):
|
|
raise UserError(_('Error with geolocation server:') + ' %s' % error)
|