geopy-2.2.0/0000755000076500000240000000000014072560236013300 5ustar kostyastaff00000000000000geopy-2.2.0/AUTHORS0000644000076500000240000001071214034400312014333 0ustar kostyastaff00000000000000Adam Tygart Adrián López Afonso Queiros Albina Alessandro Pasotti Andrea Tosatto Ann Paul Antonis Kanouras Armin Leuprecht Arsen Mamikonyan Arsen Mamikonyan Arthur Pemberton Artur avdd Azimjon Pulatov <35038240+azimjohn@users.noreply.github.com> Benjamin Henne Benjamin Trigona-Harany Benjamin Trigona-Harany Benoit Grégoire Bernd Schlapsi Brian Beck Charles Karney chilfing crccheck Dale Daniel Thul Danny Finkelstein Dave Arter David Gilman David Mueller deeplook Demeter Sztanko Dmitrii K Dody Suria Wijaya dutko.adam Edward Betts Emile Aben enrique a <13837490+enriqueav@users.noreply.github.com> Eric Palakovich Carr exogen Fabien Reboia Feanil Patel gary.bernhardt Gregory Nicholas groovecoder Hannes Hanno Schlichting Holger Bruch Ian Edwards Ian Wilson ijl ironfroggy Isaac Sijaranamual James Maddox James Mills jhmaddox Joel Natividad John.L.Clark Jon Duckworth Jonathan Batchelor Jordan Bouvier Jose Martin jqnatividad Karimov Dmitriy Kostya Esmukov Luca Marra Luke Hubbard Magnus Hiie Marc-Olivier Titeux Marco Milanesi Mariana Georgieva Martin Mateusz Konieczny Mesut Öncel Micah Cochran michal Michal Migurski Mike Hansen Mike Taves Mike Tigas Mike Toews Miltos mtmail mz navidata nucflash Oleg Oskar Hollmann Pavel Paweł Mandera Pedro Rodrigues Peter Gullekson Philip Kimmey Pratheek Rebala Przemek Malolepszy <39582596+szogoon@users.noreply.github.com> Risent Zhang Rocky Meza Ryan Nagle Sarah Hoffmann Saïd Tezel scottessner Sebastian Illing Sebastian Neubauer SemiNormal Sergey Lyapustin Sergio Martín Morillas Serphentas svalee Svetlana Konovalova Sébastien Barré TheRealZeljko Thomas Tim Gates Tom Wallroth tony tristan Vladimir Kalinkin William Hammond willr Yorick Holkamp yrafalin <31785347+yrafalin@users.noreply.github.com> zhongjun-ma <58385923+zhongjun-ma@users.noreply.github.com> Álvaro Mondéjar geopy-2.2.0/LICENSE0000644000076500000240000000206413322440422014276 0ustar kostyastaff00000000000000Copyright (c) 2006-2018 geopy authors (see AUTHORS) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. geopy-2.2.0/MANIFEST.in0000644000076500000240000000025313664116633015042 0ustar kostyastaff00000000000000recursive-include test * global-exclude *.py[co] global-exclude .DS_Store global-exclude __pycache__ include AUTHORS include LICENSE include README.rst include pytest.ini geopy-2.2.0/PKG-INFO0000644000076500000240000001437114072560236014403 0ustar kostyastaff00000000000000Metadata-Version: 2.1 Name: geopy Version: 2.2.0 Summary: Python Geocoding Toolbox Home-page: https://github.com/geopy/geopy Maintainer: Kostya Esmukov Maintainer-email: kostya@esmukov.ru License: MIT Download-URL: https://github.com/geopy/geopy/archive/2.2.0.tar.gz Description: geopy ===== .. image:: https://img.shields.io/pypi/v/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: Latest Version .. image:: https://img.shields.io/github/workflow/status/geopy/geopy/CI?style=flat-square :target: https://github.com/geopy/geopy/actions :alt: Build Status .. image:: https://img.shields.io/github/license/geopy/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: License geopy is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addresses, cities, countries, and landmarks across the globe using third-party geocoders and other data sources. geopy includes geocoder classes for the `OpenStreetMap Nominatim`_, `Google Geocoding API (V3)`_, and many other geocoding services. The full list is available on the `Geocoders doc section`_. Geocoder classes are located in `geopy.geocoders`_. .. _OpenStreetMap Nominatim: https://nominatim.org .. _Google Geocoding API (V3): https://developers.google.com/maps/documentation/geocoding/ .. _Geocoders doc section: https://geopy.readthedocs.io/en/latest/#geocoders .. _geopy.geocoders: https://github.com/geopy/geopy/tree/master/geopy/geocoders geopy is tested against CPython (versions 3.5, 3.6, 3.7, 3.8, 3.9) and PyPy3. geopy 1.x line also supported CPython 2.7, 3.4 and PyPy2. © geopy contributors 2006-2018 (see AUTHORS) under the `MIT License `__. Installation ------------ Install using `pip `__ with: :: pip install geopy Or, `download a wheel or source archive from PyPI `__. Geocoding --------- To geolocate a query to an address and coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.geocode("175 5th Avenue NYC") >>> print(location.address) Flatiron Building, 175, 5th Avenue, Flatiron, New York, NYC, New York, ... >>> print((location.latitude, location.longitude)) (40.7410861, -73.9896297241625) >>> print(location.raw) {'place_id': '9167009604', 'type': 'attraction', ...} To find the address corresponding to a set of coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.reverse("52.509669, 13.376294") >>> print(location.address) Potsdamer Platz, Mitte, Berlin, 10117, Deutschland, European Union >>> print((location.latitude, location.longitude)) (52.5094982, 13.3765983) >>> print(location.raw) {'place_id': '654513', 'osm_type': 'node', ...} Measuring Distance ------------------ Geopy can calculate geodesic distance between two points using the `geodesic distance `_ or the `great-circle distance `_, with a default of the geodesic distance available as the function `geopy.distance.distance`. Here's an example usage of the geodesic distance, taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import geodesic >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(geodesic(newport_ri, cleveland_oh).miles) 538.390445368 Using great-circle distance, also taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import great_circle >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(great_circle(newport_ri, cleveland_oh).miles) 536.997990696 Documentation ------------- More documentation and examples can be found at `Read the Docs `__. Keywords: geocode geocoding gis geographical maps earth distance Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: GIS Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.5 Provides-Extra: dev-lint Provides-Extra: timezone Provides-Extra: dev-docs Provides-Extra: aiohttp Provides-Extra: dev-test Provides-Extra: requests Provides-Extra: dev geopy-2.2.0/README.rst0000644000076500000240000000760714032167233014775 0ustar kostyastaff00000000000000geopy ===== .. image:: https://img.shields.io/pypi/v/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: Latest Version .. image:: https://img.shields.io/github/workflow/status/geopy/geopy/CI?style=flat-square :target: https://github.com/geopy/geopy/actions :alt: Build Status .. image:: https://img.shields.io/github/license/geopy/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: License geopy is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addresses, cities, countries, and landmarks across the globe using third-party geocoders and other data sources. geopy includes geocoder classes for the `OpenStreetMap Nominatim`_, `Google Geocoding API (V3)`_, and many other geocoding services. The full list is available on the `Geocoders doc section`_. Geocoder classes are located in `geopy.geocoders`_. .. _OpenStreetMap Nominatim: https://nominatim.org .. _Google Geocoding API (V3): https://developers.google.com/maps/documentation/geocoding/ .. _Geocoders doc section: https://geopy.readthedocs.io/en/latest/#geocoders .. _geopy.geocoders: https://github.com/geopy/geopy/tree/master/geopy/geocoders geopy is tested against CPython (versions 3.5, 3.6, 3.7, 3.8, 3.9) and PyPy3. geopy 1.x line also supported CPython 2.7, 3.4 and PyPy2. © geopy contributors 2006-2018 (see AUTHORS) under the `MIT License `__. Installation ------------ Install using `pip `__ with: :: pip install geopy Or, `download a wheel or source archive from PyPI `__. Geocoding --------- To geolocate a query to an address and coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.geocode("175 5th Avenue NYC") >>> print(location.address) Flatiron Building, 175, 5th Avenue, Flatiron, New York, NYC, New York, ... >>> print((location.latitude, location.longitude)) (40.7410861, -73.9896297241625) >>> print(location.raw) {'place_id': '9167009604', 'type': 'attraction', ...} To find the address corresponding to a set of coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.reverse("52.509669, 13.376294") >>> print(location.address) Potsdamer Platz, Mitte, Berlin, 10117, Deutschland, European Union >>> print((location.latitude, location.longitude)) (52.5094982, 13.3765983) >>> print(location.raw) {'place_id': '654513', 'osm_type': 'node', ...} Measuring Distance ------------------ Geopy can calculate geodesic distance between two points using the `geodesic distance `_ or the `great-circle distance `_, with a default of the geodesic distance available as the function `geopy.distance.distance`. Here's an example usage of the geodesic distance, taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import geodesic >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(geodesic(newport_ri, cleveland_oh).miles) 538.390445368 Using great-circle distance, also taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import great_circle >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(great_circle(newport_ri, cleveland_oh).miles) 536.997990696 Documentation ------------- More documentation and examples can be found at `Read the Docs `__. geopy-2.2.0/geopy/0000755000076500000240000000000014072560236014423 5ustar kostyastaff00000000000000geopy-2.2.0/geopy/__init__.py0000644000076500000240000000162014032167233016527 0ustar kostyastaff00000000000000""" geopy is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addresses, cities, countries, and landmarks across the globe using third-party geocoders and other data sources. geopy is tested against CPython (versions 3.5, 3.6, 3.7, 3.8, 3.9) and PyPy3. geopy 1.x line also supported CPython 2.7, 3.4 and PyPy2. """ from geopy.geocoders import * # noqa from geopy.location import Location # noqa from geopy.point import Point # noqa from geopy.timezone import Timezone # noqa from geopy.util import __version__, __version_info__, get_version # noqa # geopy.geocoders.options must not be importable as `geopy.options`, # because that is ambiguous (which options are that). del options # noqa # `__all__` is intentionally not defined in order to not duplicate # the same list of geocoders as in `geopy.geocoders` package. geopy-2.2.0/geopy/adapters.py0000644000076500000240000005346414034375313016612 0ustar kostyastaff00000000000000""" Adapters are HTTP client implementations used by geocoders. Some adapters might support keep-alives, request retries, http2, persistence of Cookies, response compression and so on. Adapters should be considered an implementation detail. Most of the time you wouldn't need to know about their existence unless you want to tune HTTP client settings. .. versionadded:: 2.0 Adapters are currently provided on a `provisional basis`_. .. _provisional basis: https://docs.python.org/3/glossary.html#term-provisional-api """ import abc import asyncio import contextlib import email import json import time import warnings from socket import timeout as SocketTimeout from ssl import SSLError from urllib.error import HTTPError from urllib.parse import urlparse from urllib.request import ( HTTPSHandler, ProxyHandler, Request, URLError, build_opener, getproxies, ) from geopy.exc import ( GeocoderParseError, GeocoderServiceError, GeocoderTimedOut, GeocoderUnavailable, GeopyError, ) from geopy.util import logger try: import requests from requests.adapters import HTTPAdapter as RequestsHTTPAdapter requests_available = True except ImportError: RequestsHTTPAdapter = object requests_available = False try: import aiohttp import aiohttp.client_exceptions import yarl aiohttp_available = True except ImportError: aiohttp_available = False class AdapterHTTPError(IOError): """An exception which must be raised by adapters when an HTTP response with a non-successful status code has been received. Base Geocoder class translates this exception to an instance of :class:`geopy.exc.GeocoderServiceError`. """ def __init__(self, message, *, status_code, headers, text): """ :param str message: Standard exception message. :param int status_code: HTTP status code. :param dict headers: HTTP response readers. A mapping object with lowercased or case-insensitive keys. .. versionadded:: 2.2 :param str text: HTTP body text. """ self.status_code = status_code self.headers = headers self.text = text super().__init__(message) def get_retry_after(headers): """Return Retry-After header value in seconds. .. versionadded:: 2.2 """ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After # https://github.com/urllib3/urllib3/blob/1.26.4/src/urllib3/util/retry.py#L376 try: retry_after = headers['retry-after'] except KeyError: return None if not retry_after: # None, '' return None retry_after = retry_after.strip() # RFC7231 section-7.1.3: # Retry-After = HTTP-date / delay-seconds try: # Retry-After: 120 seconds = int(retry_after) except ValueError: # Retry-After: Fri, 31 Dec 1999 23:59:59 GMT retry_date_tuple = email.utils.parsedate_tz(retry_after) if retry_date_tuple is None: logger.warning('Invalid Retry-After header: %s', retry_after) return None retry_date = email.utils.mktime_tz(retry_date_tuple) seconds = retry_date - time.time() if seconds < 0: seconds = 0 return seconds class BaseAdapter(abc.ABC): """Base class for an Adapter. There are two types of adapters: - :class:`.BaseSyncAdapter` -- synchronous adapter, - :class:`.BaseAsyncAdapter` -- asynchronous (asyncio) adapter. Concrete adapter implementations must extend one of the two base adapters above. See :attr:`geopy.geocoders.options.default_adapter_factory` for details on how to specify an adapter to be used by geocoders. """ # A class attribute which tells if this Adapter's required dependencies # are installed. By default assume that all Adapters are available. is_available = True def __init__(self, *, proxies, ssl_context): """Initialize adapter. :param dict proxies: An urllib-style proxies dict, e.g. ``{"http": "192.0.2.0:8080", "https": "192.0.2.0:8080"}``, ``{"https": "http://user:passw0rd@192.0.2.0:8080""}``. See :attr:`geopy.geocoders.options.default_proxies` (note that Adapters always receive a dict: the string proxy is transformed to dict in the base :class:`geopy.geocoders.base.Geocoder` class.). :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. """ @abc.abstractmethod def get_json(self, url, *, timeout, headers): """Same as ``get_text`` except that the response is expected to be a valid JSON. The value returned is the parsed JSON. :class:`geopy.exc.GeocoderParseError` must be raised if the response cannot be parsed. :param str url: The target URL. :param float timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict headers: A dict with custom HTTP request headers. """ @abc.abstractmethod def get_text(self, url, *, timeout, headers): """Make a GET request and return the response as string. This method should not raise any exceptions other than these: - :class:`geopy.adapters.AdapterHTTPError` should be raised if the response was successfully retrieved but the status code was non-successful. - :class:`geopy.exc.GeocoderTimedOut` should be raised when the request times out. - :class:`geopy.exc.GeocoderUnavailable` should be raised when the target host is unreachable. - :class:`geopy.exc.GeocoderServiceError` is the least specific error in the exceptions hierarchy and should be raised in any other cases. :param str url: The target URL. :param float timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict headers: A dict with custom HTTP request headers. """ class BaseSyncAdapter(BaseAdapter): """Base class for synchronous adapters. """ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass class BaseAsyncAdapter(BaseAdapter): """Base class for asynchronous adapters. See also: :ref:`Async Mode `. """ async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass def _normalize_proxies(proxies): """Normalize user-supplied `proxies`: - For `None` -- retrieve System proxies using :func:`urllib.request.getproxies` - Add `http://` scheme to proxy urls if missing. """ if proxies is None: # Use system proxy settings proxies = getproxies() if not proxies: return {} # Disable proxies normalized = {} for scheme, url in proxies.items(): if url and "://" not in url: # Without the scheme there are errors: # from aiohttp: # ValueError: Only http proxies are supported # from requests (in some envs): # urllib3.exceptions.ProxySchemeUnknown: Not supported # proxy scheme localhost url = "http://%s" % url normalized[scheme] = url return normalized class URLLibAdapter(BaseSyncAdapter): """The fallback adapter which uses urllib from the Python standard library, see :func:`urllib.request.urlopen`. urllib doesn't support keep-alives, request retries, doesn't persist Cookies and is HTTP/1.1 only. urllib was the only available option for making requests in geopy 1.x, so this adapter behaves the same as geopy 1.x in terms of HTTP requests. """ def __init__(self, *, proxies, ssl_context): proxies = _normalize_proxies(proxies) super().__init__(proxies=proxies, ssl_context=ssl_context) # `ProxyHandler` should be present even when actually there're # no proxies. `build_opener` contains it anyway. By specifying # it here explicitly we can disable system proxies (i.e. # from HTTP_PROXY env var) by setting `proxies` to `{}`. # Otherwise, if we didn't specify ProxyHandler for empty # `proxies` here, the `build_opener` would have used one internally # which could have unwillingly picked up the system proxies. opener = build_opener( HTTPSHandler(context=ssl_context), ProxyHandler(proxies), ) self.urlopen = opener.open def get_json(self, url, *, timeout, headers): text = self.get_text(url, timeout=timeout, headers=headers) try: return json.loads(text) except ValueError: raise GeocoderParseError( "Could not deserialize using deserializer:\n%s" % text ) def get_text(self, url, *, timeout, headers): req = Request(url=url, headers=headers) try: page = self.urlopen(req, timeout=timeout) except Exception as error: message = str(error.args[0]) if len(error.args) else str(error) if isinstance(error, HTTPError): code = error.getcode() response_headers = { name.lower(): value for name, value in error.headers.items() } body = self._read_http_error_body(error) raise AdapterHTTPError( message, status_code=code, headers=response_headers, text=body, ) elif isinstance(error, URLError): if "timed out" in message: raise GeocoderTimedOut("Service timed out") elif "unreachable" in message: raise GeocoderUnavailable("Service not available") elif isinstance(error, SocketTimeout): raise GeocoderTimedOut("Service timed out") elif isinstance(error, SSLError): if "timed out" in message: raise GeocoderTimedOut("Service timed out") raise GeocoderServiceError(message) else: text = self._decode_page(page) status_code = page.getcode() if status_code >= 400: response_headers = { name.lower(): value for name, value in page.headers.items() } raise AdapterHTTPError( "Non-successful status code %s" % status_code, status_code=status_code, headers=response_headers, text=text, ) return text def _read_http_error_body(self, error): try: return self._decode_page(error) except Exception: logger.debug( "Unable to fetch body for a non-successful HTTP response", exc_info=True ) return None def _decode_page(self, page): encoding = page.headers.get_content_charset() or "utf-8" try: body_bytes = page.read() except Exception: raise GeocoderServiceError("Unable to read the response") try: return str(body_bytes, encoding=encoding) except ValueError: raise GeocoderParseError("Unable to decode the response bytes") class RequestsAdapter(BaseSyncAdapter): """The adapter which uses `requests`_ library. .. _requests: https://requests.readthedocs.io `requests` supports keep-alives, retries, persists Cookies, allows response compression and uses HTTP/1.1 [currently]. ``requests`` package must be installed in order to use this adapter. """ is_available = requests_available def __init__( self, *, proxies, ssl_context, pool_connections=10, pool_maxsize=10, max_retries=2, pool_block=False ): if not requests_available: raise ImportError( "`requests` must be installed in order to use RequestsAdapter. " "If you have installed geopy via pip, you may use " "this command to install requests: " '`pip install "geopy[requests]"`.' ) proxies = _normalize_proxies(proxies) super().__init__(proxies=proxies, ssl_context=ssl_context) self.session = requests.Session() self.session.trust_env = False # don't use system proxies self.session.proxies = proxies self.session.mount( "http://", RequestsHTTPAdapter( pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries, pool_block=pool_block, ), ) self.session.mount( "https://", RequestsHTTPWithSSLContextAdapter( ssl_context=ssl_context, pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries, pool_block=pool_block, ), ) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.session.close() def __del__(self): # Cleanup keepalive connections when Geocoder (and, thus, Adapter) # instances are getting garbage-collected. session = getattr(self, "session", None) if session is not None: session.close() def get_text(self, url, *, timeout, headers): resp = self._request(url, timeout=timeout, headers=headers) return resp.text def get_json(self, url, *, timeout, headers): resp = self._request(url, timeout=timeout, headers=headers) try: return resp.json() except ValueError: raise GeocoderParseError( "Could not deserialize using deserializer:\n%s" % resp.text ) def _request(self, url, *, timeout, headers): try: resp = self.session.get(url, timeout=timeout, headers=headers) except Exception as error: message = str(error) if isinstance(error, SocketTimeout): raise GeocoderTimedOut("Service timed out") elif isinstance(error, SSLError): if "timed out" in message: raise GeocoderTimedOut("Service timed out") elif isinstance(error, requests.ConnectionError): if "unauthorized" in message.lower(): raise GeocoderServiceError(message) else: raise GeocoderUnavailable(message) elif isinstance(error, requests.Timeout): raise GeocoderTimedOut("Service timed out") raise GeocoderServiceError(message) else: if resp.status_code >= 400: raise AdapterHTTPError( "Non-successful status code %s" % resp.status_code, status_code=resp.status_code, headers=resp.headers, text=resp.text, ) return resp class AioHTTPAdapter(BaseAsyncAdapter): """The adapter which uses `aiohttp`_ library. .. _aiohttp: https://docs.aiohttp.org/ `aiohttp` supports keep-alives, persists Cookies, allows response compression and uses HTTP/1.1 [currently]. ``aiohttp`` package must be installed in order to use this adapter. """ is_available = aiohttp_available def __init__(self, *, proxies, ssl_context): if not aiohttp_available: raise ImportError( "`aiohttp` must be installed in order to use AioHTTPAdapter. " "If you have installed geopy via pip, you may use " "this command to install aiohttp: " '`pip install "geopy[aiohttp]"`.' ) proxies = _normalize_proxies(proxies) super().__init__(proxies=proxies, ssl_context=ssl_context) self.proxies = proxies self.ssl_context = ssl_context @property def session(self): # Lazy session creation, which allows to avoid "unclosed socket" # warnings if a Geocoder instance is created without entering # async context and making any requests. session = self.__dict__.get("session") if session is None: session = aiohttp.ClientSession( trust_env=False, # don't use system proxies raise_for_status=False ) self.__dict__["session"] = session return session async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): # Might issue a warning if loop is immediately closed: # ResourceWarning: unclosed transport <_SelectorSocketTransport fd=10> # https://github.com/aio-libs/aiohttp/issues/1115#issuecomment-242278593 # https://github.com/python/asyncio/issues/466 await self.session.close() async def get_text(self, url, *, timeout, headers): with self._normalize_exceptions(): async with self._request(url, timeout=timeout, headers=headers) as resp: await self._raise_for_status(resp) return await resp.text() async def get_json(self, url, *, timeout, headers): with self._normalize_exceptions(): async with self._request(url, timeout=timeout, headers=headers) as resp: await self._raise_for_status(resp) try: try: return await resp.json() except aiohttp.client_exceptions.ContentTypeError: # `Attempt to decode JSON with unexpected mimetype: # text/plain;charset=utf-8` return json.loads(await resp.text()) except ValueError: raise GeocoderParseError( "Could not deserialize using deserializer:\n%s" % (await resp.text()) ) async def _raise_for_status(self, resp): if resp.status >= 400: raise AdapterHTTPError( "Non-successful status code %s" % resp.status, status_code=resp.status, headers=resp.headers, text=await resp.text(), ) def _request(self, url, *, timeout, headers): if self.proxies: scheme = urlparse(url).scheme proxy = self.proxies.get(scheme.lower()) else: proxy = None # aiohttp accepts url as string or as yarl.URL. # A string url might be re-encoded by yarl, which might cause # a hashsum of params to change. Some geocoders use that # to authenticate their requests (such as Baidu SK). url = yarl.URL(url, encoded=True) # `encoded` param disables url re-encoding return self.session.get( url, timeout=timeout, headers=headers, proxy=proxy, ssl=self.ssl_context ) @contextlib.contextmanager def _normalize_exceptions(self): try: yield except (GeopyError, AdapterHTTPError, AssertionError): raise except Exception as error: message = str(error) if isinstance(error, asyncio.TimeoutError): raise GeocoderTimedOut("Service timed out") elif isinstance(error, SSLError): if "timed out" in message: raise GeocoderTimedOut("Service timed out") elif isinstance(error, aiohttp.ClientConnectionError): raise GeocoderUnavailable(message) raise GeocoderServiceError(message) # https://github.com/kennethreitz/requests/issues/3774#issuecomment-267871876 class RequestsHTTPWithSSLContextAdapter(RequestsHTTPAdapter): def __init__(self, *, ssl_context=None, **kwargs): self.__ssl_context = ssl_context self.__urllib3_warned = False super().__init__(**kwargs) def init_poolmanager(self, *args, **kwargs): if self.__ssl_context is not None: # This ssl context would get passed through the urllib3's # `PoolManager` up to the `HTTPSConnection` class. kwargs["ssl_context"] = self.__ssl_context self.__warn_if_old_urllib3() return super().init_poolmanager(*args, **kwargs) def proxy_manager_for(self, proxy, **proxy_kwargs): if self.__ssl_context is not None: proxy_kwargs["ssl_context"] = self.__ssl_context self.__warn_if_old_urllib3() return super().proxy_manager_for(proxy, **proxy_kwargs) def __warn_if_old_urllib3(self): if self.__urllib3_warned: return self.__urllib3_warned = True try: import requests.packages.urllib3 as urllib3 except ImportError: import urllib3 def silent_int(s): try: return int(s) except ValueError: return 0 version = tuple(silent_int(v) for v in urllib3.__version__.split(".")) if version < (1, 24, 2): warnings.warn( "urllib3 prior to 1.24.2 is known to have a bug with " "custom ssl contexts: it attempts to load system certificates " "to them. Please consider upgrading `requests` and `urllib3` " "packages. See https://github.com/urllib3/urllib3/pull/1566", UserWarning, ) def cert_verify(self, conn, url, verify, cert): super().cert_verify(conn, url, verify, cert) if self.__ssl_context is not None: # Stop requests from adding any certificates to the ssl context. conn.ca_certs = None conn.ca_cert_dir = None conn.cert_file = None conn.key_file = None geopy-2.2.0/geopy/compat.py0000644000076500000240000000023113700135050016241 0ustar kostyastaff00000000000000try: # >=3.7 from asyncio import current_task except ImportError: from asyncio import Task current_task = Task.current_task del Task geopy-2.2.0/geopy/distance.py0000644000076500000240000004516314036570421016575 0ustar kostyastaff00000000000000""" Geopy can calculate geodesic distance between two points using the `geodesic distance `_ or the `great-circle distance `_, with a default of the geodesic distance available as the function ``geopy.distance.distance``. Great-circle distance (:class:`.great_circle`) uses a spherical model of the earth, using the mean earth radius as defined by the International Union of Geodesy and Geophysics, (2\\ *a* + *b*)/3 = 6371.0087714150598 kilometers approx 6371.009 km (for WGS-84), resulting in an error of up to about 0.5%. The radius value is stored in :const:`distance.EARTH_RADIUS`, so it can be customized (it should always be in kilometers, however). The geodesic distance is the shortest distance on the surface of an ellipsoidal model of the earth. The default algorithm uses the method is given by `Karney (2013) `_ (:class:`.geodesic`); this is accurate to round-off and always converges. ``geopy.distance.distance`` currently uses :class:`.geodesic`. There are multiple popular ellipsoidal models, and which one will be the most accurate depends on where your points are located on the earth. The default is the WGS-84 ellipsoid, which is the most globally accurate. geopy includes a few other models in the :const:`distance.ELLIPSOIDS` dictionary:: model major (km) minor (km) flattening ELLIPSOIDS = {'WGS-84': (6378.137, 6356.7523142, 1 / 298.257223563), 'GRS-80': (6378.137, 6356.7523141, 1 / 298.257222101), 'Airy (1830)': (6377.563396, 6356.256909, 1 / 299.3249646), 'Intl 1924': (6378.388, 6356.911946, 1 / 297.0), 'Clarke (1880)': (6378.249145, 6356.51486955, 1 / 293.465), 'GRS-67': (6378.1600, 6356.774719, 1 / 298.25), } Here are examples of ``distance.distance`` usage, taking pair of :code:`(lat, lon)` tuples:: >>> from geopy import distance >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(distance.distance(newport_ri, cleveland_oh).miles) 538.39044536 >>> wellington = (-41.32, 174.81) >>> salamanca = (40.96, -5.50) >>> print(distance.distance(wellington, salamanca).km) 19959.6792674 Using :class:`.great_circle` distance:: >>> print(distance.great_circle(newport_ri, cleveland_oh).miles) 536.997990696 You can change the ellipsoid model used by the geodesic formulas like so:: >>> ne, cl = newport_ri, cleveland_oh >>> print(distance.geodesic(ne, cl, ellipsoid='GRS-80').miles) The above model name will automatically be retrieved from the :const:`distance.ELLIPSOIDS` dictionary. Alternatively, you can specify the model values directly:: >>> distance.geodesic(ne, cl, ellipsoid=(6377., 6356., 1 / 297.)).miles Distances support simple arithmetic, making it easy to do things like calculate the length of a path:: >>> from geopy import Nominatim >>> d = distance.distance >>> g = Nominatim(user_agent="specify_your_app_name_here") >>> _, wa = g.geocode('Washington, DC') >>> _, pa = g.geocode('Palo Alto, CA') >>> print((d(ne, cl) + d(cl, wa) + d(wa, pa)).miles) 3277.30439191 .. _distance_altitudes: Currently all algorithms assume that altitudes of the points are either zero (as in the examples above) or equal, and are relatively small. Thus altitudes never affect the resulting distances:: >>> from geopy import distance >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(distance.distance(newport_ri, cleveland_oh).km) 866.4554329098687 >>> newport_ri = (41.49008, -71.312796, 100) >>> cleveland_oh = (41.499498, -81.695391, 100) >>> print(distance.distance(newport_ri, cleveland_oh).km) 866.4554329098687 If you need to calculate distances with elevation, then for short distances the `Euclidean distance `_ formula might give a suitable approximation:: >>> import math >>> from geopy import distance >>> p1 = (43.668613, 40.258916, 0.976) >>> p2 = (43.658852, 40.250839, 1.475) >>> flat_distance = distance.distance(p1[:2], p2[:2]).km >>> print(flat_distance) 1.265133525952866 >>> euclidian_distance = math.sqrt(flat_distance**2 + (p2[2] - p1[2])**2) >>> print(euclidian_distance) 1.359986705262199 An attempt to calculate distances between points with different altitudes would result in a :class:`ValueError` exception. """ from math import asin, atan2, cos, sin, sqrt from geographiclib.geodesic import Geodesic from geopy import units, util from geopy.point import Point from geopy.units import radians # IUGG mean earth radius in kilometers, from # https://en.wikipedia.org/wiki/Earth_radius#Mean_radius. Using a # sphere with this radius results in an error of up to about 0.5%. EARTH_RADIUS = 6371.009 # From http://www.movable-type.co.uk/scripts/LatLongVincenty.html: # The most accurate and widely used globally-applicable model for the earth # ellipsoid is WGS-84, used in this script. Other ellipsoids offering a # better fit to the local geoid include Airy (1830) in the UK, International # 1924 in much of Europe, Clarke (1880) in Africa, and GRS-67 in South # America. America (NAD83) and Australia (GDA) use GRS-80, functionally # equivalent to the WGS-84 ellipsoid. ELLIPSOIDS = { # model major (km) minor (km) flattening 'WGS-84': (6378.137, 6356.7523142, 1 / 298.257223563), 'GRS-80': (6378.137, 6356.7523141, 1 / 298.257222101), 'Airy (1830)': (6377.563396, 6356.256909, 1 / 299.3249646), 'Intl 1924': (6378.388, 6356.911946, 1 / 297.0), 'Clarke (1880)': (6378.249145, 6356.51486955, 1 / 293.465), 'GRS-67': (6378.1600, 6356.774719, 1 / 298.25) } def cmp(a, b): return (a > b) - (a < b) def lonlat(x, y, z=0): """ ``geopy.distance.distance`` accepts coordinates in ``(y, x)``/``(lat, lon)`` order, while some other libraries and systems might use ``(x, y)``/``(lon, lat)``. This function provides a convenient way to convert coordinates of the ``(x, y)``/``(lon, lat)`` format to a :class:`geopy.point.Point` instance. Example:: >>> from geopy.distance import lonlat, distance >>> newport_ri_xy = (-71.312796, 41.49008) >>> cleveland_oh_xy = (-81.695391, 41.499498) >>> print(distance(lonlat(*newport_ri_xy), lonlat(*cleveland_oh_xy)).miles) 538.3904453677203 :param x: longitude :param y: latitude :param z: (optional) altitude :return: Point(latitude, longitude, altitude) """ return Point(y, x, z) def _ensure_same_altitude(a, b): if abs(a.altitude - b.altitude) > 1e-6: raise ValueError( 'Calculating distance between points with different altitudes ' 'is not supported' ) # Note: non-zero equal altitudes are fine: assuming that # the elevation is many times smaller than the Earth radius # it won't give much error. class Distance: """ Base class for other distance algorithms. Represents a distance. Can be used for units conversion:: >>> from geopy.distance import Distance >>> Distance(miles=10).km 16.09344 Distance instances have all *distance* properties from :mod:`geopy.units`, e.g.: ``km``, ``m``, ``meters``, ``miles`` and so on. Distance instances are immutable. They support comparison:: >>> from geopy.distance import Distance >>> Distance(kilometers=2) == Distance(meters=2000) True >>> Distance(kilometers=2) > Distance(miles=1) True String representation:: >>> from geopy.distance import Distance >>> repr(Distance(kilometers=2)) 'Distance(2.0)' >>> str(Distance(kilometers=2)) '2.0 km' >>> repr(Distance(miles=2)) 'Distance(3.218688)' >>> str(Distance(miles=2)) '3.218688 km' Arithmetics:: >>> from geopy.distance import Distance >>> -Distance(miles=2) Distance(-3.218688) >>> Distance(miles=2) + Distance(kilometers=1) Distance(4.218688) >>> Distance(miles=2) - Distance(kilometers=1) Distance(2.218688) >>> Distance(kilometers=6) * 5 Distance(30.0) >>> Distance(kilometers=6) / 5 Distance(1.2) """ def __init__(self, *args, **kwargs): """ There are 3 ways to create a distance: - From kilometers:: >>> from geopy.distance import Distance >>> Distance(1.42) Distance(1.42) - From units:: >>> from geopy.distance import Distance >>> Distance(kilometers=1.42) Distance(1.42) >>> Distance(miles=1) Distance(1.609344) - From points (for non-abstract distances only), calculated as a sum of distances between all points:: >>> from geopy.distance import geodesic >>> geodesic((40, 160), (40.1, 160.1)) Distance(14.003702498106215) >>> geodesic((40, 160), (40.1, 160.1), (40.2, 160.2)) Distance(27.999954644813478) """ kilometers = kwargs.pop('kilometers', 0) if len(args) == 1: # if we only get one argument we assume # it's a known distance instead of # calculating it first kilometers += args[0] elif len(args) > 1: for a, b in util.pairwise(args): kilometers += self.measure(a, b) kilometers += units.kilometers(**kwargs) self.__kilometers = kilometers def __add__(self, other): if isinstance(other, Distance): return self.__class__(self.kilometers + other.kilometers) else: raise TypeError( "Distance instance must be added with Distance instance." ) def __neg__(self): return self.__class__(-self.kilometers) def __sub__(self, other): return self + -other def __mul__(self, other): return self.__class__(self.kilometers * other) def __div__(self, other): if isinstance(other, Distance): return self.kilometers / other.kilometers else: return self.__class__(self.kilometers / other) __truediv__ = __div__ def __abs__(self): return self.__class__(abs(self.kilometers)) def __nonzero__(self): return bool(self.kilometers) __bool__ = __nonzero__ def measure(self, a, b): # Intentionally not documented, because this method is not supposed # to be used directly. raise NotImplementedError("Distance is an abstract class") def destination(self, point, bearing, distance=None): """ Calculate destination point using a starting point, bearing and a distance. This method works for non-abstract distances only. Example: a point 10 miles east from ``(34, 148)``:: >>> import geopy.distance >>> geopy.distance.distance(miles=10).destination((34, 148), bearing=90) Point(33.99987666492774, 148.17419994321995, 0.0) :param point: Starting point. :type point: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param float bearing: Bearing in degrees: 0 -- North, 90 -- East, 180 -- South, 270 or -90 -- West. :param distance: Distance, can be used to override this instance:: >>> from geopy.distance import distance, Distance >>> distance(miles=10).destination((34, 148), bearing=90, \ distance=Distance(100)) Point(33.995238229104764, 149.08238904409637, 0.0) :type distance: :class:`.Distance` :rtype: :class:`geopy.point.Point` """ raise NotImplementedError("Distance is an abstract class") def __repr__(self): # pragma: no cover return 'Distance(%s)' % self.kilometers def __str__(self): # pragma: no cover return '%s km' % self.__kilometers def __cmp__(self, other): # py2 only if isinstance(other, Distance): return cmp(self.kilometers, other.kilometers) else: return cmp(self.kilometers, other) def __eq__(self, other): return self.__cmp__(other) == 0 def __ne__(self, other): return self.__cmp__(other) != 0 def __gt__(self, other): return self.__cmp__(other) > 0 def __lt__(self, other): return self.__cmp__(other) < 0 def __ge__(self, other): return self.__cmp__(other) >= 0 def __le__(self, other): return self.__cmp__(other) <= 0 @property def feet(self): return units.feet(kilometers=self.kilometers) @property def ft(self): return self.feet @property def kilometers(self): return self.__kilometers @property def km(self): return self.kilometers @property def m(self): return self.meters @property def meters(self): return units.meters(kilometers=self.kilometers) @property def mi(self): return self.miles @property def miles(self): return units.miles(kilometers=self.kilometers) @property def nautical(self): return units.nautical(kilometers=self.kilometers) @property def nm(self): return self.nautical class great_circle(Distance): """ Use spherical geometry to calculate the surface distance between points. Set which radius of the earth to use by specifying a ``radius`` keyword argument. It must be in kilometers. The default is to use the module constant `EARTH_RADIUS`, which uses the average great-circle radius. Example:: >>> from geopy.distance import great_circle >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(great_circle(newport_ri, cleveland_oh).miles) 536.997990696 """ def __init__(self, *args, **kwargs): self.RADIUS = kwargs.pop('radius', EARTH_RADIUS) super().__init__(*args, **kwargs) def measure(self, a, b): a, b = Point(a), Point(b) _ensure_same_altitude(a, b) lat1, lng1 = radians(degrees=a.latitude), radians(degrees=a.longitude) lat2, lng2 = radians(degrees=b.latitude), radians(degrees=b.longitude) sin_lat1, cos_lat1 = sin(lat1), cos(lat1) sin_lat2, cos_lat2 = sin(lat2), cos(lat2) delta_lng = lng2 - lng1 cos_delta_lng, sin_delta_lng = cos(delta_lng), sin(delta_lng) d = atan2(sqrt((cos_lat2 * sin_delta_lng) ** 2 + (cos_lat1 * sin_lat2 - sin_lat1 * cos_lat2 * cos_delta_lng) ** 2), sin_lat1 * sin_lat2 + cos_lat1 * cos_lat2 * cos_delta_lng) return self.RADIUS * d def destination(self, point, bearing, distance=None): point = Point(point) lat1 = units.radians(degrees=point.latitude) lng1 = units.radians(degrees=point.longitude) bearing = units.radians(degrees=bearing) if distance is None: distance = self if isinstance(distance, Distance): distance = distance.kilometers d_div_r = float(distance) / self.RADIUS lat2 = asin( sin(lat1) * cos(d_div_r) + cos(lat1) * sin(d_div_r) * cos(bearing) ) lng2 = lng1 + atan2( sin(bearing) * sin(d_div_r) * cos(lat1), cos(d_div_r) - sin(lat1) * sin(lat2) ) return Point(units.degrees(radians=lat2), units.degrees(radians=lng2)) GreatCircleDistance = great_circle class geodesic(Distance): """ Calculate the geodesic distance between points. Set which ellipsoidal model of the earth to use by specifying an ``ellipsoid`` keyword argument. The default is 'WGS-84', which is the most globally accurate model. If ``ellipsoid`` is a string, it is looked up in the `ELLIPSOIDS` dictionary to obtain the major and minor semiaxes and the flattening. Otherwise, it should be a tuple with those values. See the comments above the `ELLIPSOIDS` dictionary for more information. Example:: >>> from geopy.distance import geodesic >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(geodesic(newport_ri, cleveland_oh).miles) 538.390445368 """ def __init__(self, *args, **kwargs): self.ellipsoid_key = None self.ELLIPSOID = None self.geod = None self.set_ellipsoid(kwargs.pop('ellipsoid', 'WGS-84')) major, minor, f = self.ELLIPSOID super().__init__(*args, **kwargs) def set_ellipsoid(self, ellipsoid): if isinstance(ellipsoid, str): try: self.ELLIPSOID = ELLIPSOIDS[ellipsoid] self.ellipsoid_key = ellipsoid except KeyError: raise Exception( "Invalid ellipsoid. See geopy.distance.ELLIPSOIDS" ) else: self.ELLIPSOID = ellipsoid self.ellipsoid_key = None def measure(self, a, b): a, b = Point(a), Point(b) _ensure_same_altitude(a, b) lat1, lon1 = a.latitude, a.longitude lat2, lon2 = b.latitude, b.longitude if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) s12 = self.geod.Inverse(lat1, lon1, lat2, lon2, Geodesic.DISTANCE)['s12'] return s12 def destination(self, point, bearing, distance=None): point = Point(point) lat1 = point.latitude lon1 = point.longitude azi1 = bearing if distance is None: distance = self if isinstance(distance, Distance): distance = distance.kilometers if not (isinstance(self.geod, Geodesic) and self.geod.a == self.ELLIPSOID[0] and self.geod.f == self.ELLIPSOID[2]): self.geod = Geodesic(self.ELLIPSOID[0], self.ELLIPSOID[2]) r = self.geod.Direct(lat1, lon1, azi1, distance, Geodesic.LATITUDE | Geodesic.LONGITUDE) return Point(r['lat2'], r['lon2']) GeodesicDistance = geodesic # Set the default distance formula distance = GeodesicDistance geopy-2.2.0/geopy/exc.py0000644000076500000240000000604014034400633015544 0ustar kostyastaff00000000000000""" Exceptions raised by geopy. """ class GeopyError(Exception): """ Geopy-specific exceptions are all inherited from GeopyError. """ class ConfigurationError(GeopyError, ValueError): """ When instantiating a geocoder, the arguments given were invalid. See the documentation of each geocoder's ``__init__`` for more details. """ class GeocoderServiceError(GeopyError): """ There was an exception caused when calling the remote geocoding service, and no more specific exception could be raised by geopy. When calling geocoders' ``geocode`` or `reverse` methods, this is the most generic exception that can be raised, and any non-geopy exception will be caught and turned into this. The exception's message will be that of the original exception. """ class GeocoderQueryError(GeocoderServiceError, ValueError): """ Either geopy detected input that would cause a request to fail, or a request was made and the remote geocoding service responded that the request was bad. """ class GeocoderQuotaExceeded(GeocoderServiceError): """ The remote geocoding service refused to fulfill the request because the client has used its quota. """ class GeocoderRateLimited(GeocoderQuotaExceeded, IOError): """ The remote geocoding service has rate-limited the request. Retrying later might help. Exception of this type has a ``retry_after`` attribute, which contains amount of time (in seconds) the service has asked to wait. Might be ``None`` if there were no such data in response. .. versionadded:: 2.2 """ def __init__(self, message, *, retry_after=None): super().__init__(message) self.retry_after = retry_after class GeocoderAuthenticationFailure(GeocoderServiceError): """ The remote geocoding service rejected the API key or account credentials this geocoder was instantiated with. """ class GeocoderInsufficientPrivileges(GeocoderServiceError): """ The remote geocoding service refused to fulfill a request using the account credentials given. """ class GeocoderTimedOut(GeocoderServiceError, TimeoutError): """ The call to the geocoding service was aborted because no response has been received within the ``timeout`` argument of either the geocoding class or, if specified, the method call. Some services are just consistently slow, and a higher timeout may be needed to use them. """ class GeocoderUnavailable(GeocoderServiceError, IOError): """ Either it was not possible to establish a connection to the remote geocoding service, or the service responded with a code indicating it was unavailable. """ class GeocoderParseError(GeocoderServiceError): """ Geopy could not parse the service's response. This is probably due to a bug in geopy. """ class GeocoderNotFound(GeopyError, ValueError): """ Caller requested the geocoder matching a string, e.g., ``"google"`` > ``GoogleV3``, but no geocoder could be found. """ geopy-2.2.0/geopy/extra/0000755000076500000240000000000014072560236015546 5ustar kostyastaff00000000000000geopy-2.2.0/geopy/extra/__init__.py0000644000076500000240000000017213700135050017644 0ustar kostyastaff00000000000000# Extra modules are intentionally not exported here, to avoid # them being always imported even when they are not needed. geopy-2.2.0/geopy/extra/rate_limiter.py0000644000076500000240000003461313717774171020621 0ustar kostyastaff00000000000000""":class:`.RateLimiter` and :class:`.AsyncRateLimiter` allow to perform bulk operations while gracefully handling error responses and adding delays when needed. In the example below a delay of 1 second (``min_delay_seconds=1``) will be added between each pair of ``geolocator.geocode`` calls; all :class:`geopy.exc.GeocoderServiceError` exceptions will be retried (up to ``max_retries`` times):: import pandas as pd df = pd.DataFrame({'name': ['paris', 'berlin', 'london']}) from geopy.geocoders import Nominatim geolocator = Nominatim(user_agent="specify_your_app_name_here") from geopy.extra.rate_limiter import RateLimiter geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1) df['location'] = df['name'].apply(geocode) df['point'] = df['location'].apply(lambda loc: tuple(loc.point) if loc else None) This would produce the following DataFrame:: >>> df name location \\ 0 paris (Paris, Île-de-France, France métropolitaine, ... 1 berlin (Berlin, 10117, Deutschland, (52.5170365, 13.3... 2 london (London, Greater London, England, SW1A 2DU, UK... point 0 (48.8566101, 2.3514992, 0.0) 1 (52.5170365, 13.3888599, 0.0) 2 (51.5073219, -0.1276474, 0.0) To pass extra options to the `geocode` call:: from functools import partial df['location'] = df['name'].apply(partial(geocode, language='de')) To see a progress bar:: from tqdm import tqdm tqdm.pandas() df['location'] = df['name'].progress_apply(geocode) Before using rate limiting classes, please consult with the Geocoding service ToS, which might explicitly consider bulk requests (even throttled) a violation. """ import asyncio import inspect import threading from itertools import chain, count from time import sleep from timeit import default_timer from geopy.exc import GeocoderServiceError from geopy.util import logger __all__ = ("AsyncRateLimiter", "RateLimiter") def _is_last_gen(count): """list(_is_last_gen(2)) -> [False, False, True]""" return chain((False for _ in range(count)), [True]) class BaseRateLimiter: """Base Rate Limiter class for both sync and async versions.""" _retry_exceptions = (GeocoderServiceError,) def __init__( self, *, min_delay_seconds, max_retries, swallow_exceptions, return_value_on_exception ): self.min_delay_seconds = min_delay_seconds self.max_retries = max_retries self.swallow_exceptions = swallow_exceptions self.return_value_on_exception = return_value_on_exception assert max_retries >= 0 # State: self._lock = threading.Lock() self._last_call = None def _clock(self): # pragma: no cover return default_timer() def _acquire_request_slot_gen(self): # Requests rate is limited by `min_delay_seconds` interval. # # Imagine the time axis as a grid with `min_delay_seconds` step, # where we would call each step as a "request slot". RateLimiter # guarantees that each "request slot" contains at most 1 request. # # Note that actual requests might take longer time than # `min_delay_seconds`. In that case you might want to consider # parallelizing requests (with a ThreadPool for sync mode and # asyncio tasks for async), to keep the requests rate closer # to `min_delay_seconds`. # # This generator thread-safely acquires a "request slot", and # if it fails to do that at this time, it yields the amount # of seconds to sleep until the next attempt. The generator # stops only when the "request slot" has been successfully # acquired. # # There's no ordering between the concurrent requests. The first # request to acquire the lock wins the next "request slot". while True: with self._lock: clock = self._clock() if self._last_call is None: # A first iteration -- start immediately. self._last_call = clock return seconds_since_last_call = clock - self._last_call wait = self.min_delay_seconds - seconds_since_last_call if wait <= 0: # A successfully acquired request slot. self._last_call = clock return # Couldn't acquire a request slot. Wait until the beginning # of the next slot to try again. yield wait def _retries_gen(self, args, kwargs): for i, is_last_try in zip(count(), _is_last_gen(self.max_retries)): try: yield i # Run the function. except self._retry_exceptions: if is_last_try: yield True # The exception should be raised else: logger.warning( type(self).__name__ + " caught an error, retrying " "(%s/%s tries). Called with (*%r, **%r).", i, self.max_retries, args, kwargs, exc_info=True, ) yield False # The exception has been swallowed. continue else: # A successful run -- stop retrying: return # pragma: no cover def _handle_exc(self, args, kwargs): if self.swallow_exceptions: logger.warning( type(self).__name__ + " swallowed an error after %r retries. " "Called with (*%r, **%r).", self.max_retries, args, kwargs, exc_info=True, ) return self.return_value_on_exception else: raise class RateLimiter(BaseRateLimiter): """This is a Rate Limiter implementation for synchronous functions (like geocoders with the default :class:`geopy.adapters.BaseSyncAdapter`). Examples:: from geopy.extra.rate_limiter import RateLimiter from geopy.geocoders import Nominatim geolocator = Nominatim(user_agent="specify_your_app_name_here") search = ["moscow", "paris", "berlin", "tokyo", "beijing"] geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1) locations = [geocode(s) for s in search] search = [ (55.47, 37.32), (48.85, 2.35), (52.51, 13.38), (34.69, 139.40), (39.90, 116.39) ] reverse = RateLimiter(geolocator.reverse, min_delay_seconds=1) locations = [reverse(s) for s in search] RateLimiter class is thread-safe. If geocoding service's responses are slower than `min_delay_seconds`, then you can benefit from parallelizing the work:: import concurrent.futures geolocator = OpenMapQuest(api_key="...") geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1/20) with concurrent.futures.ThreadPoolExecutor() as e: locations = list(e.map(geocode, search)) .. versionchanged:: 2.0 Added thread-safety support. """ def __init__( self, func, *, min_delay_seconds=0.0, max_retries=2, error_wait_seconds=5.0, swallow_exceptions=True, return_value_on_exception=None ): """ :param callable func: A function which should be wrapped by the rate limiter. :param float min_delay_seconds: Minimum delay in seconds between the wrapped ``func`` calls. To convert :abbr:`RPS (Requests Per Second)` rate to ``min_delay_seconds`` you need to divide 1 by RPS. For example, if you need to keep the rate at 20 RPS, you can use ``min_delay_seconds=1/20``. :param int max_retries: Number of retries on exceptions. Only :class:`geopy.exc.GeocoderServiceError` exceptions are retried -- others are always re-raised. ``max_retries + 1`` requests would be performed at max per query. Set ``max_retries=0`` to disable retries. :param float error_wait_seconds: Time to wait between retries after errors. Must be greater or equal to ``min_delay_seconds``. :param bool swallow_exceptions: Should an exception be swallowed after retries? If not, it will be re-raised. If yes, the ``return_value_on_exception`` will be returned. :param return_value_on_exception: Value to return on failure when ``swallow_exceptions=True``. """ super().__init__( min_delay_seconds=min_delay_seconds, max_retries=max_retries, swallow_exceptions=swallow_exceptions, return_value_on_exception=return_value_on_exception, ) self.func = func self.error_wait_seconds = error_wait_seconds assert error_wait_seconds >= min_delay_seconds assert max_retries >= 0 def _sleep(self, seconds): # pragma: no cover logger.debug(type(self).__name__ + " sleep(%r)", seconds) sleep(seconds) def _acquire_request_slot(self): for wait in self._acquire_request_slot_gen(): self._sleep(wait) def __call__(self, *args, **kwargs): gen = self._retries_gen(args, kwargs) for _ in gen: self._acquire_request_slot() try: res = self.func(*args, **kwargs) if inspect.isawaitable(res): raise ValueError( "An async awaitable has been passed to `RateLimiter`. " "Use `AsyncRateLimiter` instead, which supports awaitables." ) return res except self._retry_exceptions as e: if gen.throw(e): # A final try return self._handle_exc(args, kwargs) self._sleep(self.error_wait_seconds) raise RuntimeError("Should not have been reached") # pragma: no cover class AsyncRateLimiter(BaseRateLimiter): """This is a Rate Limiter implementation for asynchronous functions (like geocoders with :class:`geopy.adapters.BaseAsyncAdapter`). Examples:: from geopy.adapters import AioHTTPAdapter from geopy.extra.rate_limiter import AsyncRateLimiter from geopy.geocoders import Nominatim async with Nominatim( user_agent="specify_your_app_name_here", adapter_factory=AioHTTPAdapter, ) as geolocator: search = ["moscow", "paris", "berlin", "tokyo", "beijing"] geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1) locations = [await geocode(s) for s in search] search = [ (55.47, 37.32), (48.85, 2.35), (52.51, 13.38), (34.69, 139.40), (39.90, 116.39) ] reverse = AsyncRateLimiter(geolocator.reverse, min_delay_seconds=1) locations = [await reverse(s) for s in search] AsyncRateLimiter class is safe to use across multiple concurrent tasks. If geocoding service's responses are slower than `min_delay_seconds`, then you can benefit from parallelizing the work:: import asyncio async with OpenMapQuest( api_key="...", adapter_factory=AioHTTPAdapter ) as geolocator: geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1/20) locations = await asyncio.gather(*(geocode(s) for s in search)) .. versionadded:: 2.0 """ def __init__( self, func, *, min_delay_seconds=0.0, max_retries=2, error_wait_seconds=5.0, swallow_exceptions=True, return_value_on_exception=None ): """ :param callable func: A function which should be wrapped by the rate limiter. :param float min_delay_seconds: Minimum delay in seconds between the wrapped ``func`` calls. To convert :abbr:`RPS (Requests Per Second)` rate to ``min_delay_seconds`` you need to divide 1 by RPS. For example, if you need to keep the rate at 20 RPS, you can use ``min_delay_seconds=1/20``. :param int max_retries: Number of retries on exceptions. Only :class:`geopy.exc.GeocoderServiceError` exceptions are retried -- others are always re-raised. ``max_retries + 1`` requests would be performed at max per query. Set ``max_retries=0`` to disable retries. :param float error_wait_seconds: Time to wait between retries after errors. Must be greater or equal to ``min_delay_seconds``. :param bool swallow_exceptions: Should an exception be swallowed after retries? If not, it will be re-raised. If yes, the ``return_value_on_exception`` will be returned. :param return_value_on_exception: Value to return on failure when ``swallow_exceptions=True``. """ super().__init__( min_delay_seconds=min_delay_seconds, max_retries=max_retries, swallow_exceptions=swallow_exceptions, return_value_on_exception=return_value_on_exception, ) self.func = func self.error_wait_seconds = error_wait_seconds assert error_wait_seconds >= min_delay_seconds assert max_retries >= 0 async def _sleep(self, seconds): # pragma: no cover logger.debug(type(self).__name__ + " sleep(%r)", seconds) await asyncio.sleep(seconds) async def _acquire_request_slot(self): for wait in self._acquire_request_slot_gen(): await self._sleep(wait) async def __call__(self, *args, **kwargs): gen = self._retries_gen(args, kwargs) for _ in gen: await self._acquire_request_slot() try: return await self.func(*args, **kwargs) except self._retry_exceptions as e: if gen.throw(e): # A final try return self._handle_exc(args, kwargs) await self._sleep(self.error_wait_seconds) raise RuntimeError("Should not have been reached") # pragma: no cover geopy-2.2.0/geopy/format.py0000644000076500000240000000553013717774171016302 0ustar kostyastaff00000000000000from geopy import units # Unicode characters for symbols that appear in coordinate strings. DEGREE = chr(176) PRIME = chr(8242) DOUBLE_PRIME = chr(8243) ASCII_DEGREE = '' ASCII_PRIME = "'" ASCII_DOUBLE_PRIME = '"' LATIN1_DEGREE = chr(176) HTML_DEGREE = '°' HTML_PRIME = '′' HTML_DOUBLE_PRIME = '″' XML_DECIMAL_DEGREE = '°' XML_DECIMAL_PRIME = '′' XML_DECIMAL_DOUBLE_PRIME = '″' XML_HEX_DEGREE = '&xB0;' XML_HEX_PRIME = '&x2032;' XML_HEX_DOUBLE_PRIME = '&x2033;' ABBR_DEGREE = 'deg' ABBR_ARCMIN = 'arcmin' ABBR_ARCSEC = 'arcsec' DEGREES_FORMAT = ( "%(degrees)d%(deg)s %(minutes)d%(arcmin)s %(seconds)g%(arcsec)s" ) UNICODE_SYMBOLS = { 'deg': DEGREE, 'arcmin': PRIME, 'arcsec': DOUBLE_PRIME } ASCII_SYMBOLS = { 'deg': ASCII_DEGREE, 'arcmin': ASCII_PRIME, 'arcsec': ASCII_DOUBLE_PRIME } LATIN1_SYMBOLS = { 'deg': LATIN1_DEGREE, 'arcmin': ASCII_PRIME, 'arcsec': ASCII_DOUBLE_PRIME } HTML_SYMBOLS = { 'deg': HTML_DEGREE, 'arcmin': HTML_PRIME, 'arcsec': HTML_DOUBLE_PRIME } XML_SYMBOLS = { 'deg': XML_DECIMAL_DEGREE, 'arcmin': XML_DECIMAL_PRIME, 'arcsec': XML_DECIMAL_DOUBLE_PRIME } ABBR_SYMBOLS = { 'deg': ABBR_DEGREE, 'arcmin': ABBR_ARCMIN, 'arcsec': ABBR_ARCSEC } def format_degrees(degrees, fmt=DEGREES_FORMAT, symbols=None): """ TODO docs. """ symbols = symbols or ASCII_SYMBOLS arcminutes = units.arcminutes(degrees=degrees - int(degrees)) arcseconds = units.arcseconds(arcminutes=arcminutes - int(arcminutes)) format_dict = dict( symbols, degrees=degrees, minutes=abs(arcminutes), seconds=abs(arcseconds) ) return fmt % format_dict DISTANCE_FORMAT = "%(magnitude)s%(unit)s" DISTANCE_UNITS = { 'km': lambda d: d, 'm': lambda d: units.meters(kilometers=d), 'mi': lambda d: units.miles(kilometers=d), 'ft': lambda d: units.feet(kilometers=d), 'nm': lambda d: units.nautical(kilometers=d), 'nmi': lambda d: units.nautical(kilometers=d) } def format_distance(kilometers, fmt=DISTANCE_FORMAT, unit='km'): """ TODO docs. """ magnitude = DISTANCE_UNITS[unit](kilometers) return fmt % {'magnitude': magnitude, 'unit': unit} _DIRECTIONS = [ ('north', 'N'), ('north by east', 'NbE'), ('north-northeast', 'NNE'), ('northeast by north', 'NEbN'), ('northeast', 'NE'), ('northeast by east', 'NEbE'), ('east-northeast', 'ENE'), ('east by north', 'EbN'), ('east', 'E'), ('east by south', 'EbS'), ('east-southeast', 'ESE'), ('southeast by east', 'SEbE'), ('southeast', 'SE'), ('southeast by south', 'SEbS'), ] DIRECTIONS, DIRECTIONS_ABBR = zip(*_DIRECTIONS) ANGLE_DIRECTIONS = { n * 11.25: d for n, d in enumerate(DIRECTIONS) } ANGLE_DIRECTIONS_ABBR = { n * 11.25: d for n, d in enumerate(DIRECTIONS_ABBR) } geopy-2.2.0/geopy/geocoders/0000755000076500000240000000000014072560236016375 5ustar kostyastaff00000000000000geopy-2.2.0/geopy/geocoders/__init__.py0000644000076500000240000002645514034375174020525 0ustar kostyastaff00000000000000""" Each geolocation service you might use, such as Google Maps, Bing Maps, or Nominatim, has its own class in ``geopy.geocoders`` abstracting the service's API. Geocoders each define at least a ``geocode`` method, for resolving a location from a string, and may define a ``reverse`` method, which resolves a pair of coordinates to an address. Each Geocoder accepts any credentials or settings needed to interact with its service, e.g., an API key or locale, during its initialization. To geolocate a query to an address and coordinates: >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.geocode("175 5th Avenue NYC") >>> print(location.address) Flatiron Building, 175, 5th Avenue, Flatiron, New York, NYC, New York, ... >>> print((location.latitude, location.longitude)) (40.7410861, -73.9896297241625) >>> print(location.raw) {'place_id': '9167009604', 'type': 'attraction', ...} To find the address corresponding to a set of coordinates: >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.reverse("52.509669, 13.376294") >>> print(location.address) Potsdamer Platz, Mitte, Berlin, 10117, Deutschland, European Union >>> print((location.latitude, location.longitude)) (52.5094982, 13.3765983) >>> print(location.raw) {'place_id': '654513', 'osm_type': 'node', ...} Locators' ``geocode`` and ``reverse`` methods require the argument ``query``, and also accept at least the argument ``exactly_one``, which is ``True`` by default. Geocoders may have additional attributes, e.g., Bing accepts ``user_location``, the effect of which is to bias results near that location. ``geocode`` and ``reverse`` methods may return three types of values: - When there are no results found, returns ``None``. - When the method's ``exactly_one`` argument is ``True`` and at least one result is found, returns a :class:`geopy.location.Location` object, which can be iterated over as: ``(address, (latitude, longitude))`` Or can be accessed as ``location.address``, ``location.latitude``, ``location.longitude``, ``location.altitude``, and ``location.raw``. The last contains the full geocoder's response for this result. - When ``exactly_one`` is ``False``, and there is at least one result, returns a list of :class:`geopy.location.Location` objects, as above: ``[location, [...]]`` If a service is unavailable or otherwise returns a non-OK response, or doesn't receive a response in the allotted timeout, you will receive one of the `Exceptions`_ detailed below. .. _specifying_parameters_once: Specifying Parameters Once ~~~~~~~~~~~~~~~~~~~~~~~~~~ Geocoding methods accept a lot of different parameters, and you would probably want to specify some of them just once and not care about them later. This is easy to achieve with Python's :func:`functools.partial`:: >>> from functools import partial >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> geocode = partial(geolocator.geocode, language="es") >>> print(geocode("london")) Londres, Greater London, Inglaterra, SW1A 2DX, Gran Bretaña >>> print(geocode("paris")) París, Isla de Francia, Francia metropolitana, Francia >>> print(geocode("paris", language="en")) Paris, Ile-de-France, Metropolitan France, France >>> reverse = partial(geolocator.reverse, language="es") >>> print(reverse("52.509669, 13.376294")) Steinecke, Potsdamer Platz, Tiergarten, Mitte, 10785, Alemania If you need to modify the query, you can also use a one-liner with lambda. For example, if you only need to geocode locations in `Cleveland, Ohio`, you could do:: >>> geocode = lambda query: geolocator.geocode("%s, Cleveland OH" % query) >>> print(geocode("11111 Euclid Ave")) Thwing Center, Euclid Avenue, Magnolia-Wade Park Historic District, University Circle, Cleveland, Cuyahoga County, Ohio, 44106, United States of America That lambda doesn't accept kwargs. If you need them, you could do:: >>> _geocode = partial(geolocator.geocode, language="es") >>> geocode = lambda query, **kw: _geocode("%s, Cleveland OH" % query, **kw) >>> print(geocode("11111 Euclid Ave")) Thwing Center, Euclid Avenue, Magnolia-Wade Park Historic District, University Circle, Cleveland, Cuyahoga County, Ohio, 44106, Estados Unidos >>> print(geocode("11111 Euclid Ave", language="en")) Thwing Center, Euclid Avenue, Magnolia-Wade Park Historic District, University Circle, Cleveland, Cuyahoga County, Ohio, 44106, United States of America Geopy Is Not a Service ~~~~~~~~~~~~~~~~~~~~~~ Geocoding is provided by a number of different services, which are not affiliated with geopy in any way. These services provide APIs, which anyone could implement, and geopy is just a library which provides these implementations for many different services in a single package. .. image:: ./_static/geopy_and_geocoding_services.svg :target: ./_static/geopy_and_geocoding_services.svg Therefore: 1. Different services have different Terms of Use, quotas, pricing, geodatabases and so on. For example, :class:`.Nominatim` is free, but provides low request limits. If you need to make more queries, consider using another (probably paid) service, such as :class:`.OpenMapQuest` or :class:`.PickPoint` (these two are commercial providers of Nominatim, so they should have the same data and APIs). Or, if you are ready to wait, you can try :mod:`geopy.extra.rate_limiter`. 2. geopy cannot be responsible for the geocoding services' databases. If you have issues with some queries which the service cannot fulfill, it should be directed to that service's support team. 3. geopy cannot be responsible for any networking issues between your computer and the geocoding service. If you face any problem with your current geocoding service provider, you can always try a different one. .. _async_mode: Async Mode ~~~~~~~~~~ By default geopy geocoders are synchronous (i.e. they use an Adapter based on :class:`.BaseSyncAdapter`). All geocoders can be used with asyncio by simply switching to an Adapter based on :class:`.BaseAsyncAdapter` (like :class:`.AioHTTPAdapter`). Example:: from geopy.adapters import AioHTTPAdapter from geopy.geocoders import Nominatim async with Nominatim( user_agent="specify_your_app_name_here", adapter_factory=AioHTTPAdapter, ) as geolocator: location = await geolocator.geocode("175 5th Avenue NYC") print(location.address) Basically the usage is the same as in synchronous mode, except that all geocoder calls should be used with ``await``, and the geocoder instance should be created by ``async with``. The context manager is optional, however, it is strongly advised to use it to avoid resources leaks. """ __all__ = ( "get_geocoder_for_service", "options", # The order of classes below should correspond to the order of their # files in the ``geocoders`` directory ordered by name. # # If you're adding a new geocoder class, then you should mention it in # this module 3 times: # 1. In this ``__all__`` tuple. # 2. In the imports block below. # 3. In the ``SERVICE_TO_GEOCODER`` dict below. # # Also don't forget to pull up the list of geocoders # in the docs: docs/index.rst "AlgoliaPlaces", "ArcGIS", "AzureMaps", "Baidu", "BaiduV3", "BANFrance", "Bing", "DataBC", "GeocodeEarth", "Geocodio", "GeoNames", "GoogleV3", "Geolake", "Here", "HereV7", "IGNFrance", "MapBox", "MapQuest", "MapTiler", "Nominatim", "OpenCage", "OpenMapQuest", "PickPoint", "Pelias", "Photon", "LiveAddress", "TomTom", "What3Words", "What3WordsV3", "Yandex", ) from geopy.exc import GeocoderNotFound from geopy.geocoders.algolia import AlgoliaPlaces from geopy.geocoders.arcgis import ArcGIS from geopy.geocoders.azure import AzureMaps from geopy.geocoders.baidu import Baidu, BaiduV3 from geopy.geocoders.banfrance import BANFrance from geopy.geocoders.base import options from geopy.geocoders.bing import Bing from geopy.geocoders.databc import DataBC from geopy.geocoders.geocodeearth import GeocodeEarth from geopy.geocoders.geocodio import Geocodio from geopy.geocoders.geolake import Geolake from geopy.geocoders.geonames import GeoNames from geopy.geocoders.google import GoogleV3 from geopy.geocoders.here import Here, HereV7 from geopy.geocoders.ignfrance import IGNFrance from geopy.geocoders.mapbox import MapBox from geopy.geocoders.mapquest import MapQuest from geopy.geocoders.maptiler import MapTiler from geopy.geocoders.nominatim import Nominatim from geopy.geocoders.opencage import OpenCage from geopy.geocoders.openmapquest import OpenMapQuest from geopy.geocoders.pelias import Pelias from geopy.geocoders.photon import Photon from geopy.geocoders.pickpoint import PickPoint from geopy.geocoders.smartystreets import LiveAddress from geopy.geocoders.tomtom import TomTom from geopy.geocoders.what3words import What3Words, What3WordsV3 from geopy.geocoders.yandex import Yandex SERVICE_TO_GEOCODER = { "algolia": AlgoliaPlaces, "arcgis": ArcGIS, "azure": AzureMaps, "baidu": Baidu, "baiduv3": BaiduV3, "banfrance": BANFrance, "bing": Bing, "databc": DataBC, "geocodeearth": GeocodeEarth, "geocodio": Geocodio, "geonames": GeoNames, "google": GoogleV3, "googlev3": GoogleV3, "geolake": Geolake, "here": Here, "herev7": HereV7, "ignfrance": IGNFrance, "mapbox": MapBox, "mapquest": MapQuest, "maptiler": MapTiler, "nominatim": Nominatim, "opencage": OpenCage, "openmapquest": OpenMapQuest, "pickpoint": PickPoint, "pelias": Pelias, "photon": Photon, "liveaddress": LiveAddress, "tomtom": TomTom, "what3words": What3Words, "what3wordsv3": What3WordsV3, "yandex": Yandex, } def get_geocoder_for_service(service): """ For the service provided, try to return a geocoder class. >>> from geopy.geocoders import get_geocoder_for_service >>> get_geocoder_for_service("nominatim") geopy.geocoders.nominatim.Nominatim If the string given is not recognized, a :class:`geopy.exc.GeocoderNotFound` exception is raised. Given that almost all of the geocoders provide the ``geocode`` method it could be used to make basic queries based entirely on user input:: from geopy.geocoders import get_geocoder_for_service def geocode(geocoder, config, query): cls = get_geocoder_for_service(geocoder) geolocator = cls(**config) location = geolocator.geocode(query) return location.address >>> geocode("nominatim", dict(user_agent="specify_your_app_name_here"), \ "london") 'London, Greater London, England, SW1A 2DX, United Kingdom' >>> geocode("photon", dict(), "london") 'London, SW1A 2DX, London, England, United Kingdom' """ try: return SERVICE_TO_GEOCODER[service.lower()] except KeyError: raise GeocoderNotFound( "Unknown geocoder '%s'; options are: %s" % (service, SERVICE_TO_GEOCODER.keys()) ) geopy-2.2.0/geopy/geocoders/algolia.py0000644000076500000240000002422714034374404020364 0ustar kostyastaff00000000000000import collections.abc from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.point import Point from geopy.util import logger __all__ = ('AlgoliaPlaces',) class AlgoliaPlaces(Geocoder): """Geocoder using the Algolia Places API. Documentation at: https://community.algolia.com/places/documentation.html """ geocode_path = '/1/places/query' reverse_path = '/1/places/reverse' def __init__( self, *, app_id=None, api_key=None, domain='places-dsn.algolia.net', scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str app_id: Unique application identifier. It's used to identify you when using Algolia's API. See https://www.algolia.com/dashboard. :param str api_key: Algolia's user API key. :param str domain: Currently it is ``'places-dsn.algolia.net'``, can be changed for testing purposes. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.domain = domain.strip('/') self.app_id = app_id self.api_key = api_key self.geocode_api = ( '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) ) self.reverse_api = ( '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) ) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, type=None, restrict_searchable_attributes=None, limit=None, language=None, countries=None, around=None, around_via_ip=None, around_radius=None, x_forwarded_for=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str type: Restrict the search results to a specific type. Available types are defined in documentation: https://community.algolia.com/places/api-clients.html#api-options-type :param str restrict_searchable_attributes: Restrict the fields in which the search is done. :param int limit: Limit the maximum number of items in the response. If not provided and there are multiple results Algolia API will return 20 results by default. This will be reset to one if ``exactly_one`` is True. :param str language: If specified, restrict the search results to a single language. You can pass two letters country codes (ISO 639-1). :param list countries: If specified, restrict the search results to a specific list of countries. You can pass two letters country codes (ISO 3166-1). :param around: Force to first search around a specific latitude longitude. :type around: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool around_via_ip: Whether or not to first search around the geolocation of the user found via his IP address. This is true by default. :param int around_radius: Radius in meters to search around the latitude/longitude. Otherwise a default radius is automatically computed given the area density. :param str x_forwarded_for: Override the HTTP header X-Forwarded-For. With this you can control the source IP address used to resolve the geo-location of the user. This is particularly useful when you want to use the API from your backend as if it was from your end-users' locations. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'query': query, } if type is not None: params['type'] = type if restrict_searchable_attributes is not None: params['restrictSearchableAttributes'] = restrict_searchable_attributes if limit is not None: params['hitsPerPage'] = limit if exactly_one: params["hitsPerPage"] = 1 if language is not None: params['language'] = language.lower() if countries is not None: params['countries'] = ','.join([c.lower() for c in countries]) if around is not None: p = Point(around) params['aroundLatLng'] = "%s,%s" % (p.latitude, p.longitude) if around_via_ip is not None: params['aroundLatLngViaIP'] = \ 'true' if around_via_ip else 'false' if around_radius is not None: params['aroundRadius'] = around_radius url = '?'.join((self.geocode_api, urlencode(params))) headers = {} if x_forwarded_for is not None: headers['X-Forwarded-For'] = x_forwarded_for if self.app_id is not None and self.api_key is not None: headers['X-Algolia-Application-Id'] = self.app_id headers['X-Algolia-API-Key'] = self.api_key logger.debug('%s.geocode: %s', self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one, language=language) return self._call_geocoder(url, callback, headers=headers, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, limit=None, language=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int limit: Limit the maximum number of items in the response. If not provided and there are multiple results Algolia API will return 20 results by default. This will be reset to one if ``exactly_one`` is True. :param str language: If specified, restrict the search results to a single language. You can pass two letters country codes (ISO 639-1). :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ location = self._coerce_point_to_string(query) params = { 'aroundLatLng': location, } if limit is not None: params['hitsPerPage'] = limit if language is not None: params['language'] = language url = '?'.join((self.reverse_api, urlencode(params))) headers = {} if self.app_id is not None and self.api_key is not None: headers['X-Algolia-Application-Id'] = self.app_id headers['X-Algolia-API-Key'] = self.api_key logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one, language=language) return self._call_geocoder(url, callback, headers=headers, timeout=timeout) def _parse_feature(self, feature, language): # Parse each resource. latitude = feature.get('_geoloc', {}).get('lat') longitude = feature.get('_geoloc', {}).get('lng') if isinstance(feature['locale_names'], collections.abc.Mapping): if language in feature['locale_names']: placename = feature['locale_names'][language][0] else: placename = feature['locale_names']["default"][0] else: placename = feature['locale_names'][0] return Location(placename, (latitude, longitude), feature) def _parse_json(self, response, exactly_one, language): if response is None or 'hits' not in response: return None features = response['hits'] if not len(features): return None if exactly_one: return self._parse_feature(features[0], language=language) else: return [ self._parse_feature(feature, language=language) for feature in features ] geopy-2.2.0/geopy/geocoders/arcgis.py0000644000076500000240000002771513717774172020246 0ustar kostyastaff00000000000000import json from functools import partial from time import time from urllib.parse import urlencode from geopy.exc import ( ConfigurationError, GeocoderAuthenticationFailure, GeocoderServiceError, ) from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder, _synchronized from geopy.location import Location from geopy.util import logger __all__ = ("ArcGIS", ) DEFAULT_WKID = 4326 class ArcGIS(Geocoder): """Geocoder using the ERSI ArcGIS API. Documentation at: https://developers.arcgis.com/rest/geocode/api-reference/overview-world-geocoding-service.htm """ _TOKEN_EXPIRED = 498 auth_path = '/sharing/generateToken' geocode_path = '/arcgis/rest/services/World/GeocodeServer/findAddressCandidates' reverse_path = '/arcgis/rest/services/World/GeocodeServer/reverseGeocode' def __init__( self, username=None, password=None, *, referer=None, token_lifetime=60, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, auth_domain='www.arcgis.com', domain='geocode.arcgis.com' ): """ :param str username: ArcGIS username. Required if authenticated mode is desired. :param str password: ArcGIS password. Required if authenticated mode is desired. :param str referer: Required if authenticated mode is desired. `Referer` HTTP header to send with each request, e.g., ``'http://www.example.com'``. This is tied to an issued token, so fielding queries for multiple referrers should be handled by having multiple ArcGIS geocoder instances. :param int token_lifetime: Desired lifetime, in minutes, of an ArcGIS-issued token. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. If authenticated mode is in use, it must be ``'https'``. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str auth_domain: Domain where the target ArcGIS auth service is hosted. Used only in authenticated mode (i.e. username, password and referer are set). :param str domain: Domain where the target ArcGIS service is hosted. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) if username or password or referer: if not (username and password and referer): raise ConfigurationError( "Authenticated mode requires username," " password, and referer" ) if self.scheme != 'https': raise ConfigurationError( "Authenticated mode requires scheme of 'https'" ) self.username = username self.password = password self.referer = referer self.auth_domain = auth_domain.strip('/') self.auth_api = ( '%s://%s%s' % (self.scheme, self.auth_domain, self.auth_path) ) self.token_lifetime = token_lifetime * 60 # store in seconds self.domain = domain.strip('/') self.api = ( '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) ) self.reverse_api = ( '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) ) # Mutable state self.token = None self.token_expiry = None def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, out_fields=None): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param out_fields: A list of output fields to be returned in the attributes field of the raw data. This can be either a python list/tuple of fields or a comma-separated string. See https://developers.arcgis.com/rest/geocode/api-reference/geocoding-service-output.htm for a list of supported output fields. If you want to return all supported output fields, set ``out_fields="*"``. :type out_fields: str or iterable :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {'singleLine': query, 'f': 'json'} if exactly_one: params['maxLocations'] = 1 if out_fields is not None: if isinstance(out_fields, str): params['outFields'] = out_fields else: params['outFields'] = ",".join(out_fields) url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_geocode, exactly_one=exactly_one) return self._authenticated_call_geocoder(url, callback, timeout=timeout) def _parse_geocode(self, response, exactly_one): if 'error' in response: raise GeocoderServiceError(str(response['error'])) # Success; convert from the ArcGIS JSON format. if not len(response['candidates']): return None geocoded = [] for resource in response['candidates']: geometry = resource['location'] geocoded.append( Location( resource['address'], (geometry['y'], geometry['x']), resource ) ) if exactly_one: return geocoded[0] return geocoded def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, distance=None): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int distance: Distance from the query location, in meters, within which to search. ArcGIS has a default of 100 meters, if not specified. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ location = self._coerce_point_to_string(query, "%(lon)s,%(lat)s") wkid = DEFAULT_WKID params = {'location': location, 'f': 'json', 'outSR': wkid} if distance is not None: params['distance'] = distance url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_reverse, exactly_one=exactly_one) return self._authenticated_call_geocoder(url, callback, timeout=timeout) def _parse_reverse(self, response, exactly_one): if not len(response): return None if 'error' in response: # https://developers.arcgis.com/rest/geocode/api-reference/geocoding-service-output.htm if response['error']['code'] == 400: # 'details': ['Unable to find address for the specified location.']} try: if 'Unable to find' in response['error']['details'][0]: return None except (KeyError, IndexError): pass raise GeocoderServiceError(str(response['error'])) address = ( "%(Address)s, %(City)s, %(Region)s %(Postal)s," " %(CountryCode)s" % response['address'] ) location = Location( address, (response['location']['y'], response['location']['x']), response['address'] ) if exactly_one: return location else: return [location] def _authenticated_call_geocoder( self, url, parse_callback, *, timeout=DEFAULT_SENTINEL ): if not self.username: return self._call_geocoder(url, parse_callback, timeout=timeout) def query_callback(): call_url = "&".join((url, urlencode({"token": self.token}))) headers = {"Referer": self.referer} return self._call_geocoder( call_url, partial(maybe_reauthenticate_callback, from_token=self.token), timeout=timeout, headers=headers, ) def maybe_reauthenticate_callback(response, *, from_token): if "error" in response: if response["error"]["code"] == self._TOKEN_EXPIRED: return self._refresh_authentication_token( query_retry_callback, timeout=timeout, from_token=from_token ) return parse_callback(response) def query_retry_callback(): call_url = "&".join((url, urlencode({"token": self.token}))) headers = {"Referer": self.referer} return self._call_geocoder( call_url, parse_callback, timeout=timeout, headers=headers ) if self.token is None or int(time()) > self.token_expiry: return self._refresh_authentication_token( query_callback, timeout=timeout, from_token=self.token ) else: return query_callback() @_synchronized def _refresh_authentication_token(self, callback_success, *, timeout, from_token): if from_token != self.token: # Token has already been updated by a concurrent call. return callback_success() token_request_arguments = { 'username': self.username, 'password': self.password, 'referer': self.referer, 'expiration': self.token_lifetime, 'f': 'json' } url = "?".join((self.auth_api, urlencode(token_request_arguments))) logger.debug( "%s._refresh_authentication_token: %s", self.__class__.__name__, url ) def cb(response): if "token" not in response: raise GeocoderAuthenticationFailure( "Missing token in auth request." "Request URL: %s; response JSON: %s" % (url, json.dumps(response)) ) self.token = response["token"] self.token_expiry = int(time()) + self.token_lifetime return callback_success() return self._call_geocoder(url, cb, timeout=timeout) geopy-2.2.0/geopy/geocoders/azure.py0000644000076500000240000000431613717774171020113 0ustar kostyastaff00000000000000from geopy.geocoders.base import DEFAULT_SENTINEL from geopy.geocoders.tomtom import TomTom __all__ = ("AzureMaps", ) class AzureMaps(TomTom): """AzureMaps geocoder based on TomTom. Documentation at: https://docs.microsoft.com/en-us/azure/azure-maps/index """ geocode_path = '/search/address/json' reverse_path = '/search/address/reverse/json' def __init__( self, subscription_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, domain='atlas.microsoft.com' ): """ :param str subscription_key: Azure Maps subscription key. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str domain: Domain where the target Azure Maps service is hosted. """ super().__init__( api_key=subscription_key, scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, domain=domain, ) def _geocode_params(self, formatted_query): return { 'api-version': '1.0', 'subscription-key': self.api_key, 'query': formatted_query, } def _reverse_params(self, position): return { 'api-version': '1.0', 'subscription-key': self.api_key, 'query': position, } geopy-2.2.0/geopy/geocoders/baidu.py0000644000076500000240000002206214032167233020031 0ustar kostyastaff00000000000000import hashlib from functools import partial from urllib.parse import quote_plus, urlencode from geopy.exc import ( GeocoderAuthenticationFailure, GeocoderQueryError, GeocoderQuotaExceeded, GeocoderServiceError, ) from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Baidu", "BaiduV3") class Baidu(Geocoder): """Geocoder using the Baidu Maps v2 API. Documentation at: http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding .. attention:: Newly registered API keys will not work with v2 API, use :class:`.BaiduV3` instead. """ api_path = '/geocoder/v2/' reverse_path = '/geocoder/v2/' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, security_key=None ): """ :param str api_key: The API key (AK) required by Baidu Map to perform geocoding requests. API keys are managed through the Baidu APIs console (http://lbsyun.baidu.com/apiconsole/key). :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str security_key: The security key (SK) to calculate the SN parameter in request if authentication setting requires (http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix). """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.api = '%s://api.map.baidu.com%s' % (self.scheme, self.api_path) self.reverse_api = '%s://api.map.baidu.com%s' % (self.scheme, self.reverse_path) self.security_key = security_key def _format_components_param(self, components): """ Format the components dict to something Baidu understands. """ return "|".join( (":".join(item) for item in components.items()) ) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'ak': self.api_key, 'output': 'json', 'address': query, } url = self._construct_url(self.api, self.api_path, params) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. Baidu's API will always return at most one result. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'ak': self.api_key, 'output': 'json', 'location': self._coerce_point_to_string(query), } url = self._construct_url(self.reverse_api, self.reverse_path, params) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_reverse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_reverse_json(self, page, exactly_one=True): """ Parses a location from a single-result reverse API call. """ place = page.get('result') if not place: self._check_status(page.get('status')) return None location = place.get('formatted_address').encode('utf-8') latitude = place['location']['lat'] longitude = place['location']['lng'] location = Location(location, (latitude, longitude), place) if exactly_one: return location else: return [location] def _parse_json(self, page, exactly_one=True): """ Returns location, (latitude, longitude) from JSON feed. """ place = page.get('result') if not place: self._check_status(page.get('status')) return None def parse_place(place): """ Get the location, lat, lng from a single JSON place. """ location = place.get('level') latitude = place['location']['lat'] longitude = place['location']['lng'] return Location(location, (latitude, longitude), place) if exactly_one: return parse_place(place) else: return [parse_place(item) for item in place] def _check_status(self, status): """ Validates error statuses. """ if status == 0: # When there are no results, just return. return if status == 1: raise GeocoderServiceError( 'Internal server error.' ) elif status == 2: raise GeocoderQueryError( 'Invalid request.' ) elif status == 3: raise GeocoderAuthenticationFailure( 'Authentication failure.' ) elif status == 4: raise GeocoderQuotaExceeded( 'Quota validate failure.' ) elif status == 5: raise GeocoderQueryError( 'AK Illegal or Not Exist.' ) elif status == 101: raise GeocoderAuthenticationFailure( 'No AK' ) elif status == 102: raise GeocoderAuthenticationFailure( 'MCODE Error' ) elif status == 200: raise GeocoderAuthenticationFailure( 'Invalid AK' ) elif status == 211: raise GeocoderAuthenticationFailure( 'Invalid SN' ) elif 200 <= status < 300: raise GeocoderAuthenticationFailure( 'Authentication Failure' ) elif 300 <= status < 500: raise GeocoderQuotaExceeded( 'Quota Error.' ) else: raise GeocoderQueryError('Unknown error. Status: %r' % status) def _construct_url(self, url, path, params): query_string = urlencode(params) if self.security_key is None: return "%s?%s" % (url, query_string) else: # http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix raw = "%s?%s%s" % (path, query_string, self.security_key) sn = hashlib.md5(quote_plus(raw).encode('utf-8')).hexdigest() return "%s?%s&sn=%s" % (url, query_string, sn) class BaiduV3(Baidu): """Geocoder using the Baidu Maps v3 API. Documentation at: http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding """ api_path = '/geocoding/v3/' reverse_path = '/reverse_geocoding/v3/' geopy-2.2.0/geopy/geocoders/banfrance.py0000644000076500000240000001332214032167233020663 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("BANFrance", ) class BANFrance(Geocoder): """Geocoder using the Base Adresse Nationale France API. Documentation at: https://adresse.data.gouv.fr/api """ geocode_path = '/search' reverse_path = '/reverse' def __init__( self, *, domain='api-adresse.data.gouv.fr', scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str domain: Currently it is ``'api-adresse.data.gouv.fr'``, can be changed for testing purposes. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.domain = domain.strip('/') self.geocode_api = ( '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) ) self.reverse_api = ( '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) ) def geocode( self, query, *, limit=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param int limit: Defines the maximum number of items in the response structure. If not provided and there are multiple results the BAN API will return 5 results by default. This will be reset to one if ``exactly_one`` is True. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'q': query, } if limit is not None: params['limit'] = limit url = "?".join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: lat, lng = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { 'lat': lat, 'lng': lng, } url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_feature(self, feature): # Parse each resource. latitude = feature.get('geometry', {}).get('coordinates', [])[1] longitude = feature.get('geometry', {}).get('coordinates', [])[0] placename = feature.get('properties', {}).get('label') return Location(placename, (latitude, longitude), feature) def _parse_json(self, response, exactly_one): if response is None or 'features' not in response: return None features = response['features'] if not len(features): return None if exactly_one: return self._parse_feature(features[0]) else: return [self._parse_feature(feature) for feature in features] geopy-2.2.0/geopy/geocoders/base.py0000644000076500000240000004137214032207437017665 0ustar kostyastaff00000000000000import asyncio import functools import inspect import threading from geopy import compat from geopy.adapters import ( AdapterHTTPError, BaseAsyncAdapter, BaseSyncAdapter, RequestsAdapter, URLLibAdapter, get_retry_after, ) from geopy.exc import ( ConfigurationError, GeocoderAuthenticationFailure, GeocoderInsufficientPrivileges, GeocoderQueryError, GeocoderQuotaExceeded, GeocoderRateLimited, GeocoderServiceError, GeocoderTimedOut, ) from geopy.point import Point from geopy.util import __version__, logger __all__ = ( "Geocoder", "options", ) _DEFAULT_USER_AGENT = "geopy/%s" % __version__ _DEFAULT_ADAPTER_CLASS = next( adapter_cls for adapter_cls in (RequestsAdapter, URLLibAdapter,) if adapter_cls.is_available ) class options: """The `options` object contains default configuration values for geocoders, e.g. `timeout` and `User-Agent`. Instead of passing a custom value to each geocoder individually, you can override a default value in this object. Please note that not all geocoders use all attributes of this object. For example, some geocoders don't respect the ``default_scheme`` attribute. Refer to the specific geocoder's initializer doc for a list of parameters which that geocoder accepts. Example for overriding default ``timeout`` and ``user_agent``:: >>> import geopy.geocoders >>> from geopy.geocoders import Nominatim >>> geopy.geocoders.options.default_user_agent = 'my_app/1' >>> geopy.geocoders.options.default_timeout = 7 >>> geolocator = Nominatim() >>> print(geolocator.headers) {'User-Agent': 'my_app/1'} >>> print(geolocator.timeout) 7 Attributes: default_adapter_factory A callable which returns a :class:`geopy.adapters.BaseAdapter` instance. Adapters are different implementations of HTTP clients. See :mod:`geopy.adapters` for more info. This callable accepts two keyword args: ``proxies`` and ``ssl_context``. A class might be specified as this callable as well. Example:: import geopy.geocoders geopy.geocoders.options.default_adapter_factory \ = geopy.adapters.URLLibAdapter geopy.geocoders.options.default_adapter_factory = ( lambda proxies, ssl_context: MyAdapter( proxies=proxies, ssl_context=ssl_context, my_custom_arg=42 ) ) If `requests `_ package is installed, the default adapter is :class:`geopy.adapters.RequestsAdapter`. Otherwise it is :class:`geopy.adapters.URLLibAdapter`. .. versionadded:: 2.0 default_proxies Tunnel requests through HTTP proxy. By default the system proxies are respected (e.g. `HTTP_PROXY` and `HTTPS_PROXY` env vars or platform-specific proxy settings, such as macOS or Windows native preferences -- see :func:`urllib.request.getproxies` for more details). The `proxies` value for using system proxies is ``None``. To disable system proxies and issue requests directly, explicitly pass an empty dict as a value for `proxies`: ``{}``. To use a custom HTTP proxy location, pass a string. Valid examples are: - ``"192.0.2.0:8080"`` - ``"john:passw0rd@192.0.2.0:8080"`` - ``"http://john:passw0rd@192.0.2.0:8080"`` Please note: - Scheme part (``http://``) of the proxy is ignored. - Only `http` proxy is supported. Even if the proxy scheme is `https`, it will be ignored, and the connection between client and proxy would still be unencrypted. However, `https` requests via `http` proxy are still supported (via `HTTP CONNECT` method). Raw urllib-style `proxies` dict might be provided instead of a string: - ``{"https": "192.0.2.0:8080"}`` -- means that HTTP proxy would be used only for requests having `https` scheme. String `proxies` value is automatically used for both schemes, and is provided as a shorthand for the urllib-style `proxies` dict. For more information, see documentation on :func:`urllib.request.getproxies`. default_scheme Use ``'https'`` or ``'http'`` as the API URL's scheme. default_ssl_context An :class:`ssl.SSLContext` instance with custom TLS verification settings. Pass ``None`` to use the interpreter's defaults (that is to use the system's trusted CA certificates). To use the CA bundle used by `requests` library:: import ssl import certifi import geopy.geocoders ctx = ssl.create_default_context(cafile=certifi.where()) geopy.geocoders.options.default_ssl_context = ctx To disable TLS certificate verification completely:: import ssl import geopy.geocoders ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE geopy.geocoders.options.default_ssl_context = ctx See docs for the :class:`ssl.SSLContext` class for more examples. default_timeout Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Pass `None` to disable timeout. default_user_agent User-Agent header to send with the requests to geocoder API. """ # Please keep the attributes sorted (Sphinx sorts them in the rendered # docs) and make sure that each attr has a corresponding section in # the docstring above. # # It's bad to have the attrs docs separated from the attrs # themselves. Although Sphinx supports docstrings for each attr [1], # this is not standardized and won't work with `help()` function and # in the ReadTheDocs (at least out of the box) [2]. # # [1]: http://www.sphinx-doc.org/en/master/ext/autodoc.html#directive-autoattribute # [2]: https://github.com/rtfd/readthedocs.org/issues/855#issuecomment-261337038 default_adapter_factory = _DEFAULT_ADAPTER_CLASS default_proxies = None default_scheme = 'https' default_ssl_context = None default_timeout = 1 default_user_agent = _DEFAULT_USER_AGENT # Create an object which `repr` returns 'DEFAULT_SENTINEL'. Sphinx (docs) uses # this value when generating method's signature. DEFAULT_SENTINEL = type('object', (object,), {'__repr__': lambda self: 'DEFAULT_SENTINEL'})() ERROR_CODE_MAP = { 400: GeocoderQueryError, 401: GeocoderAuthenticationFailure, 402: GeocoderQuotaExceeded, 403: GeocoderInsufficientPrivileges, 407: GeocoderAuthenticationFailure, 408: GeocoderTimedOut, 412: GeocoderQueryError, 413: GeocoderQueryError, 414: GeocoderQueryError, 429: GeocoderRateLimited, 502: GeocoderServiceError, 503: GeocoderTimedOut, 504: GeocoderTimedOut } NONE_RESULT = object() # special return value for `_geocoder_exception_handler` class Geocoder: """ Template object for geocoders. """ def __init__( self, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): self.scheme = scheme or options.default_scheme if self.scheme not in ('http', 'https'): raise ConfigurationError( 'Supported schemes are `http` and `https`.' ) self.timeout = (timeout if timeout is not DEFAULT_SENTINEL else options.default_timeout) self.proxies = (proxies if proxies is not DEFAULT_SENTINEL else options.default_proxies) self.headers = {'User-Agent': user_agent or options.default_user_agent} self.ssl_context = (ssl_context if ssl_context is not DEFAULT_SENTINEL else options.default_ssl_context) if isinstance(self.proxies, str): self.proxies = {'http': self.proxies, 'https': self.proxies} if adapter_factory is None: adapter_factory = options.default_adapter_factory self.adapter = adapter_factory( proxies=self.proxies, ssl_context=self.ssl_context, ) if isinstance(self.adapter, BaseSyncAdapter): self.__run_async = False elif isinstance(self.adapter, BaseAsyncAdapter): self.__run_async = True else: raise ConfigurationError( "Adapter %r must extend either BaseSyncAdapter or BaseAsyncAdapter" % (type(self.adapter),) ) def __enter__(self): """Context manager for synchronous adapters. At exit all open connections will be closed. In synchronous mode context manager usage is not required, and connections will be automatically closed by garbage collection. """ if self.__run_async: raise TypeError("`async with` must be used with async adapters") res = self.adapter.__enter__() assert res is self.adapter, "adapter's __enter__ must return `self`" return self def __exit__(self, exc_type, exc_val, exc_tb): self.adapter.__exit__(exc_type, exc_val, exc_tb) async def __aenter__(self): """Context manager for asynchronous adapters. At exit all open connections will be closed. In asynchronous mode context manager usage is not required, however, it is strongly advised to avoid warnings about resources leaks. """ if not self.__run_async: raise TypeError("`async with` cannot be used with sync adapters") res = await self.adapter.__aenter__() assert res is self.adapter, "adapter's __enter__ must return `self`" return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.adapter.__aexit__(exc_type, exc_val, exc_tb) def _coerce_point_to_string(self, point, output_format="%(lat)s,%(lon)s"): """ Do the right thing on "point" input. For geocoders with reverse methods. """ if not isinstance(point, Point): point = Point(point) # Altitude is silently dropped. # # Geocoding services (almost?) always consider only lat and lon # in queries, so altitude doesn't affect the request. # A non-zero altitude should not raise an exception # though, because PoIs are assumed to span the whole # altitude axis (i.e. not just the 0km plane). return output_format % dict(lat=point.latitude, lon=point.longitude) def _format_bounding_box( self, bbox, output_format="%(lat1)s,%(lon1)s,%(lat2)s,%(lon2)s" ): """ Transform bounding box boundaries to a string matching `output_format` from the following formats: - [Point(lat1, lon1), Point(lat2, lon2)] - [[lat1, lon1], [lat2, lon2]] - ["lat1,lon1", "lat2,lon2"] It is guaranteed that lat1 <= lat2 and lon1 <= lon2. """ if len(bbox) != 2: raise GeocoderQueryError("Unsupported format for a bounding box") p1, p2 = bbox p1, p2 = Point(p1), Point(p2) return output_format % dict(lat1=min(p1.latitude, p2.latitude), lon1=min(p1.longitude, p2.longitude), lat2=max(p1.latitude, p2.latitude), lon2=max(p1.longitude, p2.longitude)) def _geocoder_exception_handler(self, error): """ Geocoder-specific exceptions handler. Override if custom exceptions processing is needed. For example, raising an appropriate GeocoderQuotaExceeded on non-200 response with a textual message in the body about the exceeded quota. Return `NONE_RESULT` to have the geocoding call return `None` (meaning empty result). """ pass def _call_geocoder( self, url, callback, *, timeout=DEFAULT_SENTINEL, is_json=True, headers=None ): """ For a generated query URL, get the results. """ req_headers = self.headers.copy() if headers: req_headers.update(headers) timeout = (timeout if timeout is not DEFAULT_SENTINEL else self.timeout) try: if is_json: result = self.adapter.get_json(url, timeout=timeout, headers=req_headers) else: result = self.adapter.get_text(url, timeout=timeout, headers=req_headers) if self.__run_async: async def fut(): try: res = callback(await result) if inspect.isawaitable(res): res = await res return res except Exception as error: res = self._adapter_error_handler(error) if res is NONE_RESULT: return None raise return fut() else: return callback(result) except Exception as error: res = self._adapter_error_handler(error) if res is NONE_RESULT: return None raise def _adapter_error_handler(self, error): if isinstance(error, AdapterHTTPError): if error.text: logger.info( 'Received an HTTP error (%s): %s', error.status_code, error.text, exc_info=False, ) res = self._geocoder_exception_handler(error) if res is NONE_RESULT: return NONE_RESULT exc_cls = ERROR_CODE_MAP.get(error.status_code, GeocoderServiceError) if issubclass(exc_cls, GeocoderRateLimited): raise exc_cls( str(error), retry_after=get_retry_after(error.headers) ) from error else: raise exc_cls(str(error)) from error else: res = self._geocoder_exception_handler(error) if res is NONE_RESULT: return NONE_RESULT # def geocode(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL): # raise NotImplementedError() # def reverse(self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL): # raise NotImplementedError() def _synchronized(func): """A decorator for geocoder methods which makes the method always run under a lock. The lock is reentrant. This decorator transparently handles sync and async working modes. """ sync_lock = threading.RLock() def locked_sync(self, *args, **kwargs): with sync_lock: return func(self, *args, **kwargs) # At the moment this decorator is evaluated we don't know if we # will work in sync or async mode. # But we shouldn't create the asyncio Lock in sync mode to avoid # unwanted implicit loop initialization. async_lock = None # asyncio.Lock() async_lock_task = None # support reentrance async def locked_async(self, *args, **kwargs): nonlocal async_lock nonlocal async_lock_task if async_lock is None: async_lock = asyncio.Lock() if async_lock.locked(): assert async_lock_task is not None if compat.current_task() is async_lock_task: res = func(self, *args, **kwargs) if inspect.isawaitable(res): res = await res return res async with async_lock: async_lock_task = compat.current_task() try: res = func(self, *args, **kwargs) if inspect.isawaitable(res): res = await res return res finally: async_lock_task = None @functools.wraps(func) def f(self, *args, **kwargs): run_async = isinstance(self.adapter, BaseAsyncAdapter) if run_async: return locked_async(self, *args, **kwargs) else: return locked_sync(self, *args, **kwargs) return f geopy-2.2.0/geopy/geocoders/bing.py0000644000076500000240000002220014032207434017654 0ustar kostyastaff00000000000000import collections.abc from functools import partial from urllib.parse import quote, urlencode from geopy.exc import ( GeocoderAuthenticationFailure, GeocoderInsufficientPrivileges, GeocoderRateLimited, GeocoderServiceError, GeocoderUnavailable, ) from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import join_filter, logger __all__ = ("Bing", ) class Bing(Geocoder): """Geocoder using the Bing Maps Locations API. Documentation at: https://msdn.microsoft.com/en-us/library/ff701715.aspx """ structured_query_params = { 'addressLine', 'locality', 'adminDistrict', 'countryRegion', 'postalCode', } geocode_path = '/REST/v1/Locations' reverse_path = '/REST/v1/Locations/%(point)s' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: Should be a valid Bing Maps API key (https://www.microsoft.com/en-us/maps/create-a-bing-maps-key). :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key domain = 'dev.virtualearth.net' self.geocode_api = '%s://%s%s' % (self.scheme, domain, self.geocode_path) self.reverse_api = '%s://%s%s' % (self.scheme, domain, self.reverse_path) def geocode( self, query, *, exactly_one=True, user_location=None, timeout=DEFAULT_SENTINEL, culture=None, include_neighborhood=None, include_country_code=False ): """ Return a location point by address. :param query: The address or query you wish to geocode. For a structured query, provide a dictionary whose keys are one of: `addressLine`, `locality` (city), `adminDistrict` (state), `countryRegion`, or `postalCode`. :type query: str or dict :param bool exactly_one: Return one result or a list of results, if available. :param user_location: Prioritize results closer to this location. :type user_location: :class:`geopy.point.Point` :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str culture: Affects the language of the response, must be a two-letter country code. :param bool include_neighborhood: Sets whether to include the neighborhood field in the response. :param bool include_country_code: Sets whether to include the two-letter ISO code of the country in the response (field name 'countryRegionIso2'). :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if isinstance(query, collections.abc.Mapping): params = { key: val for key, val in query.items() if key in self.structured_query_params } params['key'] = self.api_key else: params = { 'query': query, 'key': self.api_key } if user_location: params['userLocation'] = ",".join( (str(user_location.latitude), str(user_location.longitude)) ) if exactly_one: params['maxResults'] = 1 if culture: params['culture'] = culture if include_neighborhood is not None: params['includeNeighborhood'] = include_neighborhood if include_country_code: params['include'] = 'ciso2' # the only acceptable value url = "?".join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, culture=None, include_country_code=False ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str culture: Affects the language of the response, must be a two-letter country code. :param bool include_country_code: Sets whether to include the two-letter ISO code of the country in the response (field name 'countryRegionIso2'). :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ point = self._coerce_point_to_string(query) params = {'key': self.api_key} if culture: params['culture'] = culture if include_country_code: params['include'] = 'ciso2' # the only acceptable value quoted_point = quote(point.encode('utf-8')) url = "?".join((self.reverse_api % dict(point=quoted_point), urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, doc, exactly_one=True): """ Parse a location name, latitude, and longitude from an JSON response. """ status_code = doc.get("statusCode", 200) if status_code != 200: err = doc.get("errorDetails", "") if status_code == 401: raise GeocoderAuthenticationFailure(err) elif status_code == 403: raise GeocoderInsufficientPrivileges(err) elif status_code == 429: raise GeocoderRateLimited(err) elif status_code == 503: raise GeocoderUnavailable(err) else: raise GeocoderServiceError(err) resources = doc['resourceSets'][0]['resources'] if resources is None or not len(resources): return None def parse_resource(resource): """ Parse each return object. """ stripchars = ", \n" addr = resource['address'] address = addr.get('addressLine', '').strip(stripchars) city = addr.get('locality', '').strip(stripchars) state = addr.get('adminDistrict', '').strip(stripchars) zipcode = addr.get('postalCode', '').strip(stripchars) country = addr.get('countryRegion', '').strip(stripchars) city_state = join_filter(", ", [city, state]) place = join_filter(" ", [city_state, zipcode]) location = join_filter(", ", [address, place, country]) latitude = resource['point']['coordinates'][0] or None longitude = resource['point']['coordinates'][1] or None if latitude and longitude: latitude = float(latitude) longitude = float(longitude) return Location(location, (latitude, longitude), resource) if exactly_one: return parse_resource(resources[0]) else: return [parse_resource(resource) for resource in resources] geopy-2.2.0/geopy/geocoders/databc.py0000644000076500000240000001154513717774171020205 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.exc import GeocoderQueryError from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("DataBC", ) class DataBC(Geocoder): """Geocoder using the Physical Address Geocoder from DataBC. Documentation at: http://www.data.gov.bc.ca/dbc/geographic/locate/geocoding.page """ geocode_path = '/pub/geocoder/addresses.geojson' def __init__( self, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) domain = 'apps.gov.bc.ca' self.api = '%s://%s%s' % (self.scheme, domain, self.geocode_path) def geocode( self, query, *, max_results=25, set_back=0, location_descriptor='any', exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param int max_results: The maximum number of resutls to request. :param float set_back: The distance to move the accessPoint away from the curb (in meters) and towards the interior of the parcel. location_descriptor must be set to accessPoint for set_back to take effect. :param str location_descriptor: The type of point requested. It can be any, accessPoint, frontDoorPoint, parcelPoint, rooftopPoint and routingPoint. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {'addressString': query} if set_back != 0: params['setBack'] = set_back if location_descriptor not in ['any', 'accessPoint', 'frontDoorPoint', 'parcelPoint', 'rooftopPoint', 'routingPoint']: raise GeocoderQueryError( "You did not provided a location_descriptor " "the webservice can consume. It should be any, accessPoint, " "frontDoorPoint, parcelPoint, rooftopPoint or routingPoint." ) params['locationDescriptor'] = location_descriptor if exactly_one: max_results = 1 params['maxResults'] = max_results url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, response, exactly_one): # Success; convert from GeoJSON if not len(response['features']): return None geocoded = [] for feature in response['features']: geocoded.append(self._parse_feature(feature)) if exactly_one: return geocoded[0] return geocoded def _parse_feature(self, feature): properties = feature['properties'] coordinates = feature['geometry']['coordinates'] return Location( properties['fullAddress'], (coordinates[1], coordinates[0]), properties ) geopy-2.2.0/geopy/geocoders/geocodeearth.py0000644000076500000240000000325313717774172021416 0ustar kostyastaff00000000000000from geopy.geocoders.base import DEFAULT_SENTINEL from geopy.geocoders.pelias import Pelias __all__ = ("GeocodeEarth", ) class GeocodeEarth(Pelias): """geocode.earth, a Pelias-based service provided by the developers of Pelias itself. """ def __init__( self, api_key, *, domain='api.geocode.earth', timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, scheme=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: Geocode.earth API key, required. :param str domain: Specify a custom domain for Pelias API. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( api_key=api_key, domain=domain, timeout=timeout, proxies=proxies, user_agent=user_agent, scheme=scheme, ssl_context=ssl_context, adapter_factory=adapter_factory, ) geopy-2.2.0/geopy/geocoders/geocodio.py0000644000076500000240000001735614034374414020552 0ustar kostyastaff00000000000000import collections.abc import json from functools import partial from urllib.parse import urlencode from geopy.adapters import AdapterHTTPError from geopy.exc import GeocoderQueryError, GeocoderQuotaExceeded from geopy.geocoders.base import DEFAULT_SENTINEL, NONE_RESULT, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Geocodio", ) class Geocodio(Geocoder): """Geocoder using the Geocod.io API. Documentation at: https://www.geocod.io/docs/ Pricing details: https://www.geocod.io/pricing/ .. versionadded:: 2.2 """ structured_query_params = { 'street', 'city', 'state', 'postal_code', 'country', } domain = 'api.geocod.io' geocode_path = '/v1.6/geocode' reverse_path = '/v1.6/reverse' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: A valid Geocod.io API key. (https://dash.geocod.io/apikey/create) :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key def geocode( self, query, *, limit=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param query: The address, query or a structured query you wish to geocode. For a structured query, provide a dictionary whose keys are one of: `street`, `city`, `state`, `postal_code` or `country`. :type query: dict or str :param int limit: The maximum number of matches to return. This will be reset to 1 if ``exactly_one`` is ``True``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if isinstance(query, collections.abc.Mapping): params = { key: val for key, val in query.items() if key in self.structured_query_params } else: params = {'q': query} params['api_key'] = self.api_key if limit: params['limit'] = limit if exactly_one: params['limit'] = 1 api = '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) url = "?".join((api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, limit=None ): """Return an address by location point. :param str query: The coordinates for which you wish to obtain the closest human-readable addresses :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int limit: The maximum number of matches to return. This will be reset to 1 if ``exactly_one`` is ``True``. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'q': self._coerce_point_to_string(query), 'api_key': self.api_key } if exactly_one: limit = 1 if limit is not None: params['limit'] = limit api = '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) url = "?".join((api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, page, exactly_one=True): """Returns location, (latitude, longitude) from json feed.""" places = page.get('results', []) if not places: return None def parse_place(place): """Get the location, lat, lng from a single json place.""" location = place.get('formatted_address') latitude = place['location']['lat'] longitude = place['location']['lng'] return Location(location, (latitude, longitude), place) if exactly_one: return parse_place(places[0]) else: return [parse_place(place) for place in places] def _geocoder_exception_handler(self, error): """Custom exception handling for invalid queries and exceeded quotas. Geocod.io returns a ``422`` status code for invalid queries, which is not mapped in :const:`~geopy.geocoders.base.ERROR_CODE_MAP`. The service also returns a ``403`` status code for exceeded quotas instead of the ``429`` code mapped in :const:`~geopy.geocoders.base.ERROR_CODE_MAP` """ if not isinstance(error, AdapterHTTPError): return if error.status_code is None or error.text is None: return if error.status_code == 422: error_message = self._get_error_message(error) if ( 'could not geocode address' in error_message.lower() and 'postal code or city required' in error_message.lower() ): return NONE_RESULT raise GeocoderQueryError(error_message) from error if error.status_code == 403: error_message = self._get_error_message(error) quota_exceeded_snippet = "You can't make this request as it is " \ "above your daily maximum." if quota_exceeded_snippet in error_message: raise GeocoderQuotaExceeded(error_message) from error def _get_error_message(self, error): """Try to extract an error message from the 'error' property of a JSON response. """ try: error_message = json.loads(error.text).get('error') except ValueError: error_message = None return error_message or error.text geopy-2.2.0/geopy/geocoders/geolake.py0000644000076500000240000001270214032207434020352 0ustar kostyastaff00000000000000import collections.abc from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import join_filter, logger __all__ = ("Geolake", ) class Geolake(Geocoder): """Geocoder using the Geolake API. Documentation at: https://geolake.com/docs/api Terms of Service at: https://geolake.com/terms-of-use """ structured_query_params = { 'country', 'state', 'city', 'zipcode', 'street', 'address', 'houseNumber', 'subNumber', } api_path = '/v1/geocode' def __init__( self, api_key, *, domain='api.geolake.com', scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: The API key required by Geolake to perform geocoding requests. You can get your key here: https://geolake.com/ :param str domain: Currently it is ``'api.geolake.com'``, can be changed for testing purposes. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.api = '%s://%s%s' % (self.scheme, self.domain, self.api_path) def geocode( self, query, *, country_codes=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param query: The address or query you wish to geocode. For a structured query, provide a dictionary whose keys are one of: `country`, `state`, `city`, `zipcode`, `street`, `address`, `houseNumber` or `subNumber`. :type query: str or dict :param country_codes: Provides the geocoder with a list of country codes that the query may reside in. This value will limit the geocoder to the supplied countries. The country code is a 2 character code as defined by the ISO-3166-1 alpha-2 standard (e.g. ``FR``). Multiple countries can be specified with a Python list. :type country_codes: str or list :param bool exactly_one: Return one result or a list of one result. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if isinstance(query, collections.abc.Mapping): params = { key: val for key, val in query.items() if key in self.structured_query_params } params['api_key'] = self.api_key else: params = { 'api_key': self.api_key, 'q': query, } if not country_codes: country_codes = [] if isinstance(country_codes, str): country_codes = [country_codes] if country_codes: params['countryCodes'] = ",".join(country_codes) url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, page, exactly_one): """Returns location, (latitude, longitude) from json feed.""" if not page.get('success'): return None latitude = page['latitude'] longitude = page['longitude'] address = self._get_address(page) result = Location(address, (latitude, longitude), page) if exactly_one: return result else: return [result] def _get_address(self, page): """ Returns address string from page dictionary :param page: dict :return: str """ place = page.get('place') address_city = place.get('city') address_country_code = place.get('countryCode') address = join_filter(', ', [address_city, address_country_code]) return address geopy-2.2.0/geopy/geocoders/geonames.py0000644000076500000240000003042014032167233020540 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.exc import ( GeocoderAuthenticationFailure, GeocoderInsufficientPrivileges, GeocoderQueryError, GeocoderQuotaExceeded, GeocoderServiceError, ) from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.timezone import ( ensure_pytz_is_installed, from_fixed_gmt_offset, from_timezone_name, ) from geopy.util import logger __all__ = ("GeoNames", ) class GeoNames(Geocoder): """GeoNames geocoder. Documentation at: http://www.geonames.org/export/geonames-search.html Reverse geocoding documentation at: http://www.geonames.org/export/web-services.html#findNearbyPlaceName """ geocode_path = '/searchJSON' reverse_path = '/findNearbyPlaceNameJSON' reverse_nearby_path = '/findNearbyJSON' timezone_path = '/timezoneJSON' def __init__( self, username, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, scheme='http' ): """ :param str username: GeoNames username, required. Sign up here: http://www.geonames.org/login :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. Note that at the time of writing GeoNames doesn't support `https`, so the default scheme is `http`. The value of :attr:`geopy.geocoders.options.default_scheme` is not respected. This parameter is present to make it possible to switch to `https` once GeoNames adds support for it. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.username = username domain = 'api.geonames.org' self.api = ( "%s://%s%s" % (self.scheme, domain, self.geocode_path) ) self.api_reverse = ( "%s://%s%s" % (self.scheme, domain, self.reverse_path) ) self.api_reverse_nearby = ( "%s://%s%s" % (self.scheme, domain, self.reverse_nearby_path) ) self.api_timezone = ( "%s://%s%s" % (self.scheme, domain, self.timezone_path) ) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, country=None, country_bias=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param country: Limit records to the specified countries. Two letter country code ISO-3166 (e.g. ``FR``). Might be a single string or a list of strings. :type country: str or list :param str country_bias: Records from the country_bias are listed first. Two letter country code ISO-3166. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = [ ('q', query), ('username', self.username), ] if country_bias: params.append(('countryBias', country_bias)) if not country: country = [] if isinstance(country, str): country = [country] for country_item in country: params.append(('country', country_item)) if exactly_one: params.append(('maxRows', 1)) url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, feature_code=None, lang=None, find_nearby_type='findNearbyPlaceName' ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str feature_code: A GeoNames feature code :param str lang: language of the returned ``name`` element (the pseudo language code 'local' will return it in local language) Full list of supported languages can be found here: https://www.geonames.org/countries/ :param str find_nearby_type: A flag to switch between different GeoNames API endpoints. The default value is ``findNearbyPlaceName`` which returns the closest populated place. Another currently implemented option is ``findNearby`` which returns the closest toponym for the lat/lng query. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: lat, lng = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") if find_nearby_type == 'findNearbyPlaceName': # default if feature_code: raise ValueError( "find_nearby_type=findNearbyPlaceName doesn't support " "the `feature_code` param" ) params = self._reverse_find_nearby_place_name_params( lat=lat, lng=lng, lang=lang, ) url = "?".join((self.api_reverse, urlencode(params))) elif find_nearby_type == 'findNearby': if lang: raise ValueError( "find_nearby_type=findNearby doesn't support the `lang` param" ) params = self._reverse_find_nearby_params( lat=lat, lng=lng, feature_code=feature_code, ) url = "?".join((self.api_reverse_nearby, urlencode(params))) else: raise GeocoderQueryError( '`%s` find_nearby_type is not supported by geopy' % find_nearby_type ) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _reverse_find_nearby_params(self, lat, lng, feature_code): params = { 'lat': lat, 'lng': lng, 'username': self.username, } if feature_code: params['featureCode'] = feature_code return params def _reverse_find_nearby_place_name_params(self, lat, lng, lang): params = { 'lat': lat, 'lng': lng, 'username': self.username, } if lang: params['lang'] = lang return params def reverse_timezone(self, query, *, timeout=DEFAULT_SENTINEL): """ Find the timezone for a point in `query`. GeoNames always returns a timezone: if the point being queried doesn't have an assigned Olson timezone id, a ``pytz.FixedOffset`` timezone is used to produce the :class:`geopy.timezone.Timezone`. :param query: The coordinates for which you want a timezone. :type query: :class:`geopy.point.Point`, list or tuple of (latitude, longitude), or string as "%(latitude)s, %(longitude)s" :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: :class:`geopy.timezone.Timezone`. """ ensure_pytz_is_installed() try: lat, lng = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { "lat": lat, "lng": lng, "username": self.username, } url = "?".join((self.api_timezone, urlencode(params))) logger.debug("%s.reverse_timezone: %s", self.__class__.__name__, url) return self._call_geocoder(url, self._parse_json_timezone, timeout=timeout) def _raise_for_error(self, body): err = body.get('status') if err: code = err['value'] message = err['message'] # http://www.geonames.org/export/webservice-exception.html if message.startswith("user account not enabled to use"): raise GeocoderInsufficientPrivileges(message) if code == 10: raise GeocoderAuthenticationFailure(message) if code in (18, 19, 20): raise GeocoderQuotaExceeded(message) raise GeocoderServiceError(message) def _parse_json_timezone(self, response): self._raise_for_error(response) timezone_id = response.get("timezoneId") if timezone_id is None: # Sometimes (e.g. for Antarctica) GeoNames doesn't return # a `timezoneId` value, but it returns GMT offsets. # Apparently GeoNames always returns these offsets -- for # every single point on the globe. raw_offset = response["rawOffset"] return from_fixed_gmt_offset(raw_offset, raw=response) else: return from_timezone_name(timezone_id, raw=response) def _parse_json(self, doc, exactly_one): """ Parse JSON response body. """ places = doc.get('geonames', []) self._raise_for_error(doc) if not len(places): return None def parse_code(place): """ Parse each record. """ latitude = place.get('lat', None) longitude = place.get('lng', None) if latitude and longitude: latitude = float(latitude) longitude = float(longitude) else: return None placename = place.get('name') state = place.get('adminName1', None) country = place.get('countryName', None) location = ', '.join( [x for x in [placename, state, country] if x] ) return Location(location, (latitude, longitude), place) if exactly_one: return parse_code(places[0]) else: return [parse_code(place) for place in places] geopy-2.2.0/geopy/geocoders/google.py0000644000076500000240000003722314034375174020235 0ustar kostyastaff00000000000000import base64 import collections.abc import hashlib import hmac from calendar import timegm from datetime import datetime from functools import partial from urllib.parse import urlencode from geopy.exc import ConfigurationError, GeocoderQueryError, GeocoderQuotaExceeded from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.timezone import ensure_pytz_is_installed, from_timezone_name from geopy.util import logger __all__ = ("GoogleV3", ) class GoogleV3(Geocoder): """Geocoder using the Google Maps v3 API. Documentation at: https://developers.google.com/maps/documentation/geocoding/ Pricing details: https://developers.google.com/maps/documentation/geocoding/usage-and-billing """ api_path = '/maps/api/geocode/json' timezone_path = '/maps/api/timezone/json' def __init__( self, api_key=None, *, domain='maps.googleapis.com', scheme=None, client_id=None, secret_key=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, channel='' ): """ :param str api_key: The API key required by Google to perform geocoding requests, mandatory (unless premier is used, then both ``client_id`` and ``secret_key`` must be specified instead). API keys are managed through the Google APIs console (https://code.google.com/apis/console). Make sure to have both ``Geocoding API`` and ``Time Zone API`` services enabled for this API key. .. versionchanged:: 2.1 Previously a warning has been emitted when neither ``api_key`` nor premier were specified. Now a :class:`geopy.exc.ConfigurationError` is raised. :param str domain: Should be the localized Google Maps domain to connect to. The default is 'maps.googleapis.com', but if you're geocoding address in the UK (for example), you may want to set it to 'maps.google.co.uk' to properly bias results. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param str client_id: If using premier, the account client id. :param str secret_key: If using premier, the account secret key. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str channel: If using premier, the channel identifier. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) if client_id and not secret_key: raise ConfigurationError('Must provide secret_key with client_id.') if secret_key and not client_id: raise ConfigurationError('Must provide client_id with secret_key.') self.premier = bool(client_id and secret_key) self.client_id = client_id self.secret_key = secret_key if not self.premier and not api_key: raise ConfigurationError( 'Since July 2018 Google requires each request to have an API key. ' 'Pass a valid `api_key` to GoogleV3 geocoder to fix this error. ' 'See https://developers.google.com/maps/documentation/geocoding/usage-and-billing' # noqa ) self.api_key = api_key self.domain = domain.strip('/') self.channel = channel self.api = '%s://%s%s' % (self.scheme, self.domain, self.api_path) self.tz_api = '%s://%s%s' % (self.scheme, self.domain, self.timezone_path) def _get_signed_url(self, params): """ Returns a Premier account signed url. Docs on signature: https://developers.google.com/maps/documentation/business/webservices/auth#digital_signatures """ params['client'] = self.client_id if self.channel: params['channel'] = self.channel path = "?".join((self.api_path, urlencode(params))) signature = hmac.new( base64.urlsafe_b64decode(self.secret_key), path.encode('utf-8'), hashlib.sha1 ) signature = base64.urlsafe_b64encode( signature.digest() ).decode('utf-8') return '%s://%s%s&signature=%s' % ( self.scheme, self.domain, path, signature ) def _format_components_param(self, components): """ Format the components dict to something Google understands. """ component_items = [] if isinstance(components, collections.abc.Mapping): component_items = components.items() elif ( isinstance(components, collections.abc.Sequence) and not isinstance(components, (str, bytes)) ): component_items = components else: raise ValueError( '`components` parameter must be of type `dict` or `list`') return "|".join( (":".join(item) for item in component_items) ) def geocode( self, query=None, *, exactly_one=True, timeout=DEFAULT_SENTINEL, bounds=None, region=None, components=None, place_id=None, language=None, sensor=False ): """ Return a location point by address. :param str query: The address or query you wish to geocode. Optional, if ``components`` param is set:: >>> g.geocode(components={"city": "Paris", "country": "FR"}) Location(France, (46.227638, 2.213749, 0.0)) :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :type bounds: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param bounds: The bounding box of the viewport within which to bias geocode results more prominently. Example: ``[Point(22, 180), Point(-22, -180)]``. :param str region: The region code, specified as a ccTLD ("top-level domain") two-character value. :type components: dict or list :param components: Restricts to an area. Can use any combination of: `route`, `locality`, `administrative_area`, `postal_code`, `country`. Pass a list of tuples if you want to specify multiple components of the same type, e.g.: >>> [('administrative_area', 'VA'), ('administrative_area', 'Arlington')] :param str place_id: Retrieve a Location using a Place ID. Cannot be not used with ``query`` or ``bounds`` parameters. >>> g.geocode(place_id='ChIJOcfP0Iq2j4ARDrXUa7ZWs34') :param str language: The language in which to return results. :param bool sensor: Whether the geocoding request comes from a device with a location sensor. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'sensor': str(sensor).lower() } if place_id and (bounds or query): raise ValueError( 'Only one of the `query` or `place id` or `bounds` ' ' parameters must be entered.') if place_id is not None: params['place_id'] = place_id if query is not None: params['address'] = query if query is None and place_id is None and not components: raise ValueError('Either `query` or `components` or `place_id` ' 'must be set.') if self.api_key: params['key'] = self.api_key if bounds: params['bounds'] = self._format_bounding_box( bounds, "%(lat1)s,%(lon1)s|%(lat2)s,%(lon2)s") if region: params['region'] = region if components: params['components'] = self._format_components_param(components) if language: params['language'] = language if self.premier: url = self._get_signed_url(params) else: url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=None, sensor=False ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str language: The language in which to return results. :param bool sensor: Whether the geocoding request comes from a device with a location sensor. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'latlng': self._coerce_point_to_string(query), 'sensor': str(sensor).lower() } if language: params['language'] = language if self.api_key: params['key'] = self.api_key if not self.premier: url = "?".join((self.api, urlencode(params))) else: url = self._get_signed_url(params) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse_timezone(self, query, *, at_time=None, timeout=DEFAULT_SENTINEL): """ Find the timezone a point in `query` was in for a specified `at_time`. `None` will be returned for points without an assigned Olson timezone id (e.g. for Antarctica). :param query: The coordinates for which you want a timezone. :type query: :class:`geopy.point.Point`, list or tuple of (latitude, longitude), or string as "%(latitude)s, %(longitude)s" :param at_time: The time at which you want the timezone of this location. This is optional, and defaults to the time that the function is called in UTC. Timezone-aware datetimes are correctly handled and naive datetimes are silently treated as UTC. :type at_time: :class:`datetime.datetime` or None :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None`` or :class:`geopy.timezone.Timezone`. """ ensure_pytz_is_installed() location = self._coerce_point_to_string(query) timestamp = self._normalize_timezone_at_time(at_time) params = { "location": location, "timestamp": timestamp, } if self.api_key: params['key'] = self.api_key url = "?".join((self.tz_api, urlencode(params))) logger.debug("%s.reverse_timezone: %s", self.__class__.__name__, url) return self._call_geocoder(url, self._parse_json_timezone, timeout=timeout) def _parse_json_timezone(self, response): status = response.get('status') if status != 'OK': self._check_status(status) timezone_id = response.get("timeZoneId") if timezone_id is None: # Google returns `status: ZERO_RESULTS` for uncovered # points (e.g. for Antarctica), so there's nothing # meaningful to be returned as the `raw` response, # hence we return `None`. return None return from_timezone_name(timezone_id, raw=response) def _normalize_timezone_at_time(self, at_time): if at_time is None: timestamp = timegm(datetime.utcnow().utctimetuple()) elif isinstance(at_time, datetime): # Naive datetimes are silently treated as UTC. # Timezone-aware datetimes are handled correctly. timestamp = timegm(at_time.utctimetuple()) else: raise GeocoderQueryError( "`at_time` must be an instance of `datetime.datetime`" ) return timestamp def _parse_json(self, page, exactly_one=True): '''Returns location, (latitude, longitude) from json feed.''' places = page.get('results', []) if not len(places): self._check_status(page.get('status')) return None def parse_place(place): '''Get the location, lat, lng from a single json place.''' location = place.get('formatted_address') latitude = place['geometry']['location']['lat'] longitude = place['geometry']['location']['lng'] return Location(location, (latitude, longitude), place) if exactly_one: return parse_place(places[0]) else: return [parse_place(place) for place in places] def _check_status(self, status): # https://developers.google.com/maps/documentation/geocoding/overview#StatusCodes if status == 'ZERO_RESULTS': # When there are no results, just return. return if status in ('OVER_QUERY_LIMIT', 'OVER_DAILY_LIMIT'): raise GeocoderQuotaExceeded( 'The given key has gone over the requests limit in the 24' ' hour period or has submitted too many requests in too' ' short a period of time.' ) elif status == 'REQUEST_DENIED': raise GeocoderQueryError( 'Your request was denied.' ) elif status == 'INVALID_REQUEST': raise GeocoderQueryError('Probably missing address or latlng.') else: raise GeocoderQueryError('Unknown error.') geopy-2.2.0/geopy/geocoders/googlev3.py0000644000076500000240000000044614034375174020503 0ustar kostyastaff00000000000000import warnings from geopy.geocoders.google import GoogleV3 __all__ = ("GoogleV3",) warnings.warn( "`geopy.geocoders.googlev3` module is deprecated. " "Use `geopy.geocoders.google` instead. " "In geopy 3 this module will be removed.", DeprecationWarning, stacklevel=2, ) geopy-2.2.0/geopy/geocoders/here.py0000644000076500000240000005657214034374414017710 0ustar kostyastaff00000000000000import collections.abc import json import warnings from functools import partial from urllib.parse import urlencode from geopy.adapters import AdapterHTTPError from geopy.exc import ( ConfigurationError, GeocoderAuthenticationFailure, GeocoderInsufficientPrivileges, GeocoderQueryError, GeocoderRateLimited, GeocoderServiceError, GeocoderUnavailable, ) from geopy.geocoders.base import DEFAULT_SENTINEL, ERROR_CODE_MAP, Geocoder from geopy.location import Location from geopy.util import join_filter, logger __all__ = ("Here", "HereV7") class Here(Geocoder): """Geocoder using the HERE Geocoder API. Documentation at: https://developer.here.com/documentation/geocoder/ .. attention:: This class uses a v6 API which is in maintenance mode. Consider using the newer :class:`.HereV7` class. """ structured_query_params = { 'city', 'county', 'district', 'country', 'state', 'street', 'housenumber', 'postalcode', } geocode_path = '/6.2/geocode.json' reverse_path = '/6.2/reversegeocode.json' def __init__( self, *, app_id=None, app_code=None, apikey=None, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str app_id: Should be a valid HERE Maps APP ID. Will eventually be replaced with APIKEY. See https://developer.here.com/authenticationpage. .. attention:: App ID and App Code are being replaced by API Keys and OAuth 2.0 by HERE. Consider getting an ``apikey`` instead of using ``app_id`` and ``app_code``. :param str app_code: Should be a valid HERE Maps APP CODE. Will eventually be replaced with APIKEY. See https://developer.here.com/authenticationpage. .. attention:: App ID and App Code are being replaced by API Keys and OAuth 2.0 by HERE. Consider getting an ``apikey`` instead of using ``app_id`` and ``app_code``. :param str apikey: Should be a valid HERE Maps APIKEY. These keys were introduced in December 2019 and will eventually replace the legacy APP CODE/APP ID pairs which are already no longer available for new accounts (but still work for old accounts). More authentication details are available at https://developer.here.com/blog/announcing-two-new-authentication-types. See https://developer.here.com/authenticationpage. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) is_apikey = bool(apikey) is_app_code = app_id and app_code if not is_apikey and not is_app_code: raise ConfigurationError( "HERE geocoder requires authentication, either `apikey` " "or `app_id`+`app_code` must be set" ) if is_app_code: warnings.warn( 'Since December 2019 HERE provides two new authentication ' 'methods `API Key` and `OAuth 2.0`. `app_id`+`app_code` ' 'is deprecated and might eventually be phased out. ' 'Consider switching to `apikey`, which geopy supports. ' 'See https://developer.here.com/blog/announcing-two-new-authentication-types', # noqa UserWarning, stacklevel=2 ) self.app_id = app_id self.app_code = app_code self.apikey = apikey domain = "ls.hereapi.com" if is_apikey else "api.here.com" self.api = "%s://geocoder.%s%s" % (self.scheme, domain, self.geocode_path) self.reverse_api = ( "%s://reverse.geocoder.%s%s" % (self.scheme, domain, self.reverse_path) ) def geocode( self, query, *, bbox=None, mapview=None, exactly_one=True, maxresults=None, pageinformation=None, language=None, additional_data=False, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. This implementation supports only a subset of all available parameters. A list of all parameters of the pure REST API is available here: https://developer.here.com/documentation/geocoder/topics/resource-geocode.html :param query: The address or query you wish to geocode. For a structured query, provide a dictionary whose keys are one of: `city`, `county`, `district`, `country`, `state`, `street`, `housenumber`, or `postalcode`. :type query: str or dict :param bbox: A type of spatial filter, limits the search for any other attributes in the request. Specified by two coordinate (lat/lon) pairs -- corners of the box. `The bbox search is currently similar to mapview but it is not extended` (cited from the REST API docs). Relevant global results are also returned. Example: ``[Point(22, 180), Point(-22, -180)]``. :type bbox: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param mapview: The app's viewport, given as two coordinate pairs, specified by two lat/lon pairs -- corners of the bounding box, respectively. Matches from within the set map view plus an extended area are ranked highest. Relevant global results are also returned. Example: ``[Point(22, 180), Point(-22, -180)]``. :type mapview: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int maxresults: Defines the maximum number of items in the response structure. If not provided and there are multiple results the HERE API will return 10 results by default. This will be reset to one if ``exactly_one`` is True. :param int pageinformation: A key which identifies the page to be returned when the response is separated into multiple pages. Only useful when ``maxresults`` is also provided. :param str language: Affects the language of the response, must be a RFC 4647 language code, e.g. 'en-US'. :param str additional_data: A string with key-value pairs as described on https://developer.here.com/documentation/geocoder/topics/resource-params-additional.html. These will be added as one query parameter to the URL. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if isinstance(query, collections.abc.Mapping): params = { key: val for key, val in query.items() if key in self.structured_query_params } else: params = {'searchtext': query} if bbox: params['bbox'] = self._format_bounding_box( bbox, "%(lat2)s,%(lon1)s;%(lat1)s,%(lon2)s") if mapview: params['mapview'] = self._format_bounding_box( mapview, "%(lat2)s,%(lon1)s;%(lat1)s,%(lon2)s") if pageinformation: params['pageinformation'] = pageinformation if maxresults: params['maxresults'] = maxresults if exactly_one: params['maxresults'] = 1 if language: params['language'] = language if additional_data: params['additionaldata'] = additional_data if self.apikey: params['apiKey'] = self.apikey else: params['app_id'] = self.app_id params['app_code'] = self.app_code url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, radius=None, exactly_one=True, maxresults=None, pageinformation=None, language=None, mode='retrieveAddresses', timeout=DEFAULT_SENTINEL ): """ Return an address by location point. This implementation supports only a subset of all available parameters. A list of all parameters of the pure REST API is available here: https://developer.here.com/documentation/geocoder/topics/resource-reverse-geocode.html :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param float radius: Proximity radius in meters. :param bool exactly_one: Return one result or a list of results, if available. :param int maxresults: Defines the maximum number of items in the response structure. If not provided and there are multiple results the HERE API will return 10 results by default. This will be reset to one if ``exactly_one`` is True. :param int pageinformation: A key which identifies the page to be returned when the response is separated into multiple pages. Only useful when ``maxresults`` is also provided. :param str language: Affects the language of the response, must be a RFC 4647 language code, e.g. 'en-US'. :param str mode: Affects the type of returned response items, must be one of: 'retrieveAddresses' (default), 'retrieveAreas', 'retrieveLandmarks', 'retrieveAll', or 'trackPosition'. See online documentation for more information. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ point = self._coerce_point_to_string(query) params = { 'mode': mode, 'prox': point, } if radius is not None: params['prox'] = '%s,%s' % (params['prox'], float(radius)) if pageinformation: params['pageinformation'] = pageinformation if maxresults: params['maxresults'] = maxresults if exactly_one: params['maxresults'] = 1 if language: params['language'] = language if self.apikey: params['apiKey'] = self.apikey else: params['app_id'] = self.app_id params['app_code'] = self.app_code url = "%s?%s" % (self.reverse_api, urlencode(params)) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, doc, exactly_one=True): """ Parse a location name, latitude, and longitude from an JSON response. """ status_code = doc.get("statusCode", 200) if status_code != 200: err = doc.get("errorDetails", "") if status_code == 401: raise GeocoderAuthenticationFailure(err) elif status_code == 403: raise GeocoderInsufficientPrivileges(err) elif status_code == 429: raise GeocoderRateLimited(err) elif status_code == 503: raise GeocoderUnavailable(err) else: raise GeocoderServiceError(err) try: resources = doc['Response']['View'][0]['Result'] except IndexError: resources = None if not resources: return None def parse_resource(resource): """ Parse each return object. """ stripchars = ", \n" addr = resource['Location']['Address'] address = addr.get('Label', '').strip(stripchars) city = addr.get('City', '').strip(stripchars) state = addr.get('State', '').strip(stripchars) zipcode = addr.get('PostalCode', '').strip(stripchars) country = addr.get('Country', '').strip(stripchars) city_state = join_filter(", ", [city, state]) place = join_filter(" ", [city_state, zipcode]) location = join_filter(", ", [address, place, country]) display_pos = resource['Location']['DisplayPosition'] latitude = float(display_pos['Latitude']) longitude = float(display_pos['Longitude']) return Location(location, (latitude, longitude), resource) if exactly_one: return parse_resource(resources[0]) else: return [parse_resource(resource) for resource in resources] class HereV7(Geocoder): """Geocoder using the HERE Geocoding & Search v7 API. Documentation at: https://developer.here.com/documentation/geocoding-search-api/ Terms of Service at: https://legal.here.com/en-gb/terms .. versionadded:: 2.2 """ structured_query_params = { 'country', 'state', 'county', 'city', 'district', 'street', 'houseNumber', 'postalCode', } geocode_path = '/v1/geocode' reverse_path = '/v1/revgeocode' def __init__( self, apikey, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str apikey: Should be a valid HERE Maps apikey. A project can be created at https://developer.here.com/projects. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) domain = "search.hereapi.com" self.apikey = apikey self.api = "%s://geocode.%s%s" % (self.scheme, domain, self.geocode_path) self.reverse_api = ( "%s://revgeocode.%s%s" % (self.scheme, domain, self.reverse_path) ) def geocode( self, query=None, *, components=None, at=None, countries=None, language=None, limit=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The address or query you wish to geocode. Optional, if ``components`` param is set. :param dict components: A structured query. Can be used along with the free-text ``query``. Should be a dictionary whose keys are one of: `country`, `state`, `county`, `city`, `district`, `street`, `houseNumber`, `postalCode`. :param at: The center of the search context. :type at: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param list countries: A list of country codes specified in `ISO 3166-1 alpha-3 `_ format, e.g. ``['USA', 'CAN']``. This is a hard filter. :param str language: Affects the language of the response, must be a BCP 47 compliant language code, e.g. ``en-US``. :param int limit: Defines the maximum number of items in the response structure. If not provided and there are multiple results the HERE API will return 20 results by default. This will be reset to one if ``exactly_one`` is True. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'apiKey': self.apikey, } if query: params['q'] = query if components: parts = [ "{}={}".format(key, val) for key, val in components.items() if key in self.structured_query_params ] if not parts: raise GeocoderQueryError("`components` dict must not be empty") for pair in parts: if ';' in pair: raise GeocoderQueryError( "';' must not be used in values of the structured query. " "Offending pair: {!r}".format(pair) ) params['qq'] = ';'.join(parts) if at: point = self._coerce_point_to_string(at, output_format="%(lat)s,%(lon)s") params['at'] = point if countries: params['in'] = 'countryCode:' + ','.join(countries) if language: params['lang'] = language if limit: params['limit'] = limit if exactly_one: params['limit'] = 1 url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, language=None, limit=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param str language: Affects the language of the response, must be a BCP 47 compliant language code, e.g. ``en-US``. :param int limit: Maximum number of results to be returned. This will be reset to one if ``exactly_one`` is True. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'at': self._coerce_point_to_string(query, output_format="%(lat)s,%(lon)s"), 'apiKey': self.apikey, } if language: params['lang'] = language if limit: params['limit'] = limit if exactly_one: params['limit'] = 1 url = "%s?%s" % (self.reverse_api, urlencode(params)) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, doc, exactly_one=True): resources = doc['items'] if not resources: return None def parse_resource(resource): """ Parse each return object. """ location = resource['title'] position = resource['position'] latitude, longitude = position['lat'], position['lng'] return Location(location, (latitude, longitude), resource) if exactly_one: return parse_resource(resources[0]) else: return [parse_resource(resource) for resource in resources] def _geocoder_exception_handler(self, error): if not isinstance(error, AdapterHTTPError): return if error.status_code is None or error.text is None: return try: body = json.loads(error.text) except ValueError: message = error.text else: # `title`: https://developer.here.com/documentation/geocoding-search-api/api-reference-swagger.html # noqa # `error_description`: returned for queries without apiKey. message = body.get('title') or body.get('error_description') or error.text exc_cls = ERROR_CODE_MAP.get(error.status_code, GeocoderServiceError) raise exc_cls(message) from error geopy-2.2.0/geopy/geocoders/ignfrance.py0000644000076500000240000004321614032167233020705 0ustar kostyastaff00000000000000import base64 import xml.etree.ElementTree as ET from functools import partial from urllib.parse import urlencode from geopy.exc import ConfigurationError, GeocoderQueryError from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("IGNFrance", ) class IGNFrance(Geocoder): """Geocoder using the IGN France GeoCoder OpenLS API. Documentation at: https://geoservices.ign.fr/documentation/geoservices/index.html """ xml_request = """ {sub_request} """ api_path = '/%(api_key)s/geoportail/ols' def __init__( self, api_key, *, username=None, password=None, referer=None, domain='wxs.ign.fr', scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: The API key required by IGN France API to perform geocoding requests. You can get your key here: https://geoservices.ign.fr/documentation/services-acces.html. Mandatory. For authentication with referer and with username/password, the api key always differ. :param str username: When making a call need HTTP simple authentication username. Mandatory if no referer set :param str password: When making a call need HTTP simple authentication password. Mandatory if no referer set :param str referer: When making a call need HTTP referer. Mandatory if no password and username :param str domain: Currently it is ``'wxs.ign.fr'``, can be changed for testing purposes for developer API e.g ``'gpp3-wxs.ign.fr'`` at the moment. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) # Catch if no api key with username and password # or no api key with referer if not ((api_key and username and password) or (api_key and referer)): raise ConfigurationError('You should provide an api key and a ' 'username with a password or an api ' 'key with a referer depending on ' 'created api key') if (username and password) and referer: raise ConfigurationError('You can\'t set username/password and ' 'referer together. The API key always ' 'differs depending on both scenarios') if username and not password: raise ConfigurationError( 'username and password must be set together' ) self.api_key = api_key self.username = username self.password = password self.referer = referer self.domain = domain.strip('/') api_path = self.api_path % dict(api_key=self.api_key) self.api = '%s://%s%s' % (self.scheme, self.domain, api_path) def geocode( self, query, *, query_type='StreetAddress', maximum_responses=25, is_freeform=False, filtering=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The query string to be geocoded. :param str query_type: The type to provide for geocoding. It can be `PositionOfInterest`, `StreetAddress` or `CadastralParcel`. `StreetAddress` is the default choice if none provided. :param int maximum_responses: The maximum number of responses to ask to the API in the query body. :param str is_freeform: Set if return is structured with freeform structure or a more structured returned. By default, value is False. :param str filtering: Provide string that help setting geocoder filter. It contains an XML string. See examples in documentation and ignfrance.py file in directory tests. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ # Check if acceptable query type if query_type not in ['PositionOfInterest', 'StreetAddress', 'CadastralParcel']: raise GeocoderQueryError("""You did not provided a query_type the webservice can consume. It should be PositionOfInterest, 'StreetAddress or CadastralParcel""") # Check query validity for CadastralParcel if query_type == 'CadastralParcel' and len(query.strip()) != 14: raise GeocoderQueryError("""You must send a string of fourteen characters long to match the cadastre required code""") sub_request = """
{query} {filtering}
""" xml_request = self.xml_request.format( method_name='LocationUtilityService', sub_request=sub_request, maximum_responses=maximum_responses ) # Manage type change for xml case sensitive if is_freeform: is_freeform = 'true' else: is_freeform = 'false' # Manage filtering value if filtering is None: filtering = '' # Create query using parameters request_string = xml_request.format( is_freeform=is_freeform, query=query, query_type=query_type, filtering=filtering ) params = { 'xls': request_string } url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial( self._parse_xml, is_freeform=is_freeform, exactly_one=exactly_one ) return self._request_raw_content(url, callback, timeout=timeout) def reverse( self, query, *, reverse_geocode_preference=('StreetAddress', ), maximum_responses=25, filtering='', exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param list reverse_geocode_preference: Enable to set expected results type. It can be `StreetAddress` or `PositionOfInterest`. Default is set to `StreetAddress`. :param int maximum_responses: The maximum number of responses to ask to the API in the query body. :param str filtering: Provide string that help setting geocoder filter. It contains an XML string. See examples in documentation and ignfrance.py file in directory tests. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ sub_request = """ {reverse_geocode_preference} {query} {filtering} """ xml_request = self.xml_request.format( method_name='ReverseGeocodeRequest', sub_request=sub_request, maximum_responses=maximum_responses ) for pref in reverse_geocode_preference: if pref not in ('StreetAddress', 'PositionOfInterest'): raise GeocoderQueryError( '`reverse_geocode_preference` must contain ' 'one or more of: StreetAddress, PositionOfInterest' ) point = self._coerce_point_to_string(query, "%(lat)s %(lon)s") reverse_geocode_preference = '\n'.join(( '%s' % pref for pref in reverse_geocode_preference )) request_string = xml_request.format( maximum_responses=maximum_responses, query=point, reverse_geocode_preference=reverse_geocode_preference, filtering=filtering ) url = "?".join((self.api, urlencode({'xls': request_string}))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial( self._parse_xml, exactly_one=exactly_one, is_reverse=True, is_freeform='false' ) return self._request_raw_content(url, callback, timeout=timeout) def _parse_xml(self, page, is_reverse=False, is_freeform=False, exactly_one=True): """ Returns location, (latitude, longitude) from XML feed and transform to json """ # Parse the page tree = ET.fromstring(page.encode('utf-8')) # Clean tree from namespace to facilitate XML manipulation def remove_namespace(doc, namespace): """Remove namespace in the document in place.""" ns = '{%s}' % namespace nsl = len(ns) for elem in doc.iter(): if elem.tag.startswith(ns): elem.tag = elem.tag[nsl:] remove_namespace(tree, 'http://www.opengis.net/gml') remove_namespace(tree, 'http://www.opengis.net/xls') remove_namespace(tree, 'http://www.opengis.net/xlsext') # Return places as json instead of XML places = self._xml_to_json_places(tree, is_reverse=is_reverse) if not places: return None if exactly_one: return self._parse_place(places[0], is_freeform=is_freeform) else: return [ self._parse_place( place, is_freeform=is_freeform ) for place in places ] def _xml_to_json_places(self, tree, is_reverse=False): """ Transform the xml ElementTree due to XML webservice return to json """ select_multi = ( 'GeocodedAddress' if not is_reverse else 'ReverseGeocodedLocation' ) adresses = tree.findall('.//' + select_multi) places = [] sel_pl = './/Address/Place[@type="{}"]' for adr in adresses: el = {} el['pos'] = adr.find('./Point/pos') el['street'] = adr.find('.//Address/StreetAddress/Street') el['freeformaddress'] = adr.find('.//Address/freeFormAddress') el['municipality'] = adr.find(sel_pl.format('Municipality')) el['numero'] = adr.find(sel_pl.format('Numero')) el['feuille'] = adr.find(sel_pl.format('Feuille')) el['section'] = adr.find(sel_pl.format('Section')) el['departement'] = adr.find(sel_pl.format('Departement')) el['commune_absorbee'] = adr.find(sel_pl.format('CommuneAbsorbee')) el['commune'] = adr.find(sel_pl.format('Commune')) el['insee'] = adr.find(sel_pl.format('INSEE')) el['qualite'] = adr.find(sel_pl.format('Qualite')) el['territoire'] = adr.find(sel_pl.format('Territoire')) el['id'] = adr.find(sel_pl.format('ID')) el['id_tr'] = adr.find(sel_pl.format('ID_TR')) el['bbox'] = adr.find(sel_pl.format('Bbox')) el['nature'] = adr.find(sel_pl.format('Nature')) el['postal_code'] = adr.find('.//Address/PostalCode') el['extended_geocode_match_code'] = adr.find( './/ExtendedGeocodeMatchCode' ) place = {} def testContentAttrib(selector, key): """ Helper to select by attribute and if not attribute, value set to empty string """ return selector.attrib.get( key, None ) if selector is not None else None place['accuracy'] = testContentAttrib( adr.find('.//GeocodeMatchCode'), 'accuracy') place['match_type'] = testContentAttrib( adr.find('.//GeocodeMatchCode'), 'matchType') place['building'] = testContentAttrib( adr.find('.//Address/StreetAddress/Building'), 'number') place['search_centre_distance'] = testContentAttrib( adr.find('.//SearchCentreDistance'), 'value') for key, value in iter(el.items()): if value is not None: place[key] = value.text else: place[key] = None # We check if lat lng is not empty and unpack accordingly if place['pos']: lat, lng = place['pos'].split(' ') place['lat'] = lat.strip() place['lng'] = lng.strip() else: place['lat'] = place['lng'] = None # We removed the unused key place.pop("pos", None) places.append(place) return places def _request_raw_content(self, url, callback, *, timeout): """ Send the request to get raw content. """ headers = {} if self.referer is not None: headers['Referer'] = self.referer if self.username and self.password and self.referer is None: credentials = '{0}:{1}'.format(self.username, self.password).encode() auth_str = base64.standard_b64encode(credentials).decode() headers['Authorization'] = 'Basic {}'.format(auth_str.strip()) return self._call_geocoder( url, callback, headers=headers, timeout=timeout, is_json=False, ) def _parse_place(self, place, is_freeform=None): """ Get the location, lat, lng and place from a single json place. """ # When freeform already so full address if is_freeform == 'true': location = place.get('freeformaddress') else: # For parcelle if place.get('numero'): location = place.get('street') else: # When classic geocoding # or when reverse geocoding location = "%s %s" % ( place.get('postal_code', ''), place.get('commune', ''), ) if place.get('street'): location = "%s, %s" % ( place.get('street', ''), location, ) if place.get('building'): location = "%s %s" % ( place.get('building', ''), location, ) return Location(location, (place.get('lat'), place.get('lng')), place) geopy-2.2.0/geopy/geocoders/mapbox.py0000644000076500000240000001526313717774172020257 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import quote, urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.point import Point from geopy.util import logger __all__ = ("MapBox", ) class MapBox(Geocoder): """Geocoder using the Mapbox API. Documentation at: https://www.mapbox.com/api-documentation/ """ api_path = '/geocoding/v5/mapbox.places/%(query)s.json/' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, domain='api.mapbox.com' ): """ :param str api_key: The API key required by Mapbox to perform geocoding requests. API keys are managed through Mapox's account page (https://www.mapbox.com/account/access-tokens). :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str domain: base api domain for mapbox """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.api = "%s://%s%s" % (self.scheme, self.domain, self.api_path) def _parse_json(self, json, exactly_one=True): '''Returns location, (latitude, longitude) from json feed.''' features = json['features'] if features == []: return None def parse_feature(feature): location = feature['place_name'] longitude = feature['geometry']['coordinates'][0] latitude = feature['geometry']['coordinates'][1] return Location(location, (latitude, longitude), feature) if exactly_one: return parse_feature(features[0]) else: return [parse_feature(feature) for feature in features] def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, proximity=None, country=None, bbox=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param proximity: A coordinate to bias local results based on a provided location. :type proximity: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param country: Country to filter result in form of ISO 3166-1 alpha-2 country code (e.g. ``FR``). Might be a Python list of strings. :type country: str or list :param bbox: The bounding box of the viewport within which to bias geocode results more prominently. Example: ``[Point(22, 180), Point(-22, -180)]``. :type bbox: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {} params['access_token'] = self.api_key if bbox: params['bbox'] = self._format_bounding_box( bbox, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") if not country: country = [] if isinstance(country, str): country = [country] if country: params['country'] = ",".join(country) if proximity: p = Point(proximity) params['proximity'] = "%s,%s" % (p.longitude, p.latitude) quoted_query = quote(query.encode('utf-8')) url = "?".join((self.api % dict(query=quoted_query), urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {} params['access_token'] = self.api_key point = self._coerce_point_to_string(query, "%(lon)s,%(lat)s") quoted_query = quote(point.encode('utf-8')) url = "?".join((self.api % dict(query=quoted_query), urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) geopy-2.2.0/geopy/geocoders/mapquest.py0000644000076500000240000001566413717774171020634 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("MapQuest", ) class MapQuest(Geocoder): """Geocoder using the MapQuest API based on Licensed data. Documentation at: https://developer.mapquest.com/documentation/geocoding-api/ MapQuest provides two Geocoding APIs: - :class:`geopy.geocoders.OpenMapQuest` Nominatim-alike API which is based on Open data from OpenStreetMap. - :class:`geopy.geocoders.MapQuest` (this class) MapQuest's own API which is based on Licensed data. """ geocode_path = '/geocoding/v1/address' reverse_path = '/geocoding/v1/reverse' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, domain='www.mapquestapi.com' ): """ :param str api_key: The API key required by Mapquest to perform geocoding requests. API keys are managed through MapQuest's "Manage Keys" page (https://developer.mapquest.com/user/me/apps). :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str domain: base api domain for mapquest """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.geocode_api = ( '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) ) self.reverse_api = ( '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) ) def _parse_json(self, json, exactly_one=True): '''Returns location, (latitude, longitude) from json feed.''' features = json['results'][0]['locations'] if features == []: return None def parse_location(feature): addr_keys = [ 'street', 'adminArea6', 'adminArea5', 'adminArea4', 'adminArea3', 'adminArea2', 'adminArea1', 'postalCode' ] location = [feature[k] for k in addr_keys if feature.get(k)] return ", ".join(location) def parse_feature(feature): location = parse_location(feature) longitude = feature['latLng']['lng'] latitude = feature['latLng']['lat'] return Location(location, (latitude, longitude), feature) if exactly_one: return parse_feature(features[0]) else: return [parse_feature(feature) for feature in features] def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, limit=None, bounds=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int limit: Limit the maximum number of items in the response. This will be reset to one if ``exactly_one`` is True. :param bounds: The bounding box of the viewport within which to bias geocode results more prominently. Example: ``[Point(22, 180), Point(-22, -180)]``. :type bounds: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {} params['key'] = self.api_key params['location'] = query if limit is not None: params['maxResults'] = limit if exactly_one: params["maxResults"] = 1 if bounds: params['boundingBox'] = self._format_bounding_box( bounds, "%(lat2)s,%(lon1)s,%(lat1)s,%(lon2)s" ) url = '?'.join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {} params['key'] = self.api_key point = self._coerce_point_to_string(query, "%(lat)s,%(lon)s") params['location'] = point url = '?'.join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) geopy-2.2.0/geopy/geocoders/maptiler.py0000644000076500000240000001563113717774171020604 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import quote, urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.point import Point from geopy.util import logger __all__ = ("MapTiler", ) class MapTiler(Geocoder): """Geocoder using the MapTiler API. Documentation at: https://cloud.maptiler.com/geocoding/ (requires sign-up) """ api_path = '/geocoding/%(query)s.json' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, domain='api.maptiler.com' ): """ :param str api_key: The API key required by Maptiler to perform geocoding requests. API keys are managed through Maptiler's account page (https://cloud.maptiler.com/account/keys). :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str domain: base api domain for Maptiler """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.api = "%s://%s%s" % (self.scheme, self.domain, self.api_path) def _parse_json(self, json, exactly_one=True): # Returns location, (latitude, longitude) from json feed. features = json['features'] if not features: return None def parse_feature(feature): location = feature['place_name'] longitude = feature['center'][0] latitude = feature['center'][1] return Location(location, (latitude, longitude), feature) if exactly_one: return parse_feature(features[0]) else: return [parse_feature(feature) for feature in features] def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, proximity=None, language=None, bbox=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param proximity: A coordinate to bias local results based on a provided location. :type proximity: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param language: Prefer results in specific languages. Accepts a single string like ``"en"`` or a list like ``["de", "en"]``. :type language: str or list :param bbox: The bounding box of the viewport within which to bias geocode results more prominently. Example: ``[Point(22, 180), Point(-22, -180)]``. :type bbox: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {'key': self.api_key} query = query if bbox: params['bbox'] = self._format_bounding_box( bbox, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") if isinstance(language, str): language = [language] if language: params['language'] = ','.join(language) if proximity: p = Point(proximity) params['proximity'] = "%s,%s" % (p.longitude, p.latitude) quoted_query = quote(query.encode('utf-8')) url = "?".join((self.api % dict(query=quoted_query), urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param language: Prefer results in specific languages. Accepts a single string like ``"en"`` or a list like ``["de", "en"]``. :type language: str or list :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {'key': self.api_key} if isinstance(language, str): language = [language] if language: params['language'] = ','.join(language) point = self._coerce_point_to_string(query, "%(lon)s,%(lat)s") quoted_query = quote(point.encode('utf-8')) url = "?".join((self.api % dict(query=quoted_query), urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) geopy-2.2.0/geopy/geocoders/nominatim.py0000644000076500000240000003311314032167233020737 0ustar kostyastaff00000000000000import collections.abc from functools import partial from urllib.parse import urlencode from geopy.exc import ConfigurationError, GeocoderQueryError from geopy.geocoders.base import _DEFAULT_USER_AGENT, DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Nominatim", ) _DEFAULT_NOMINATIM_DOMAIN = 'nominatim.openstreetmap.org' _REJECTED_USER_AGENTS = ( # Various sample user-agent strings mentioned in docs: "my-application", "my_app/1", "my_user_agent/1.0", "specify_your_app_name_here", _DEFAULT_USER_AGENT, ) class Nominatim(Geocoder): """Nominatim geocoder for OpenStreetMap data. Documentation at: https://nominatim.org/release-docs/develop/api/Overview/ .. attention:: Using Nominatim with the default `user_agent` is strongly discouraged, as it violates Nominatim's Usage Policy https://operations.osmfoundation.org/policies/nominatim/ and may possibly cause 403 and 429 HTTP errors. Please make sure to specify a custom `user_agent` with ``Nominatim(user_agent="my-application")`` or by overriding the default `user_agent`: ``geopy.geocoders.options.default_user_agent = "my-application"``. An exception will be thrown if a custom `user_agent` is not specified. """ structured_query_params = { 'street', 'city', 'county', 'state', 'country', 'postalcode', } geocode_path = '/search' reverse_path = '/reverse' def __init__( self, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, domain=_DEFAULT_NOMINATIM_DOMAIN, scheme=None, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None # Make sure to synchronize the changes of this signature in the # inheriting classes (e.g. PickPoint). ): """ :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str domain: Domain where the target Nominatim service is hosted. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.domain = domain.strip('/') if (self.domain == _DEFAULT_NOMINATIM_DOMAIN and self.headers['User-Agent'] in _REJECTED_USER_AGENTS): raise ConfigurationError( 'Using Nominatim with default or sample `user_agent` "%s" is ' 'strongly discouraged, as it violates Nominatim\'s ToS ' 'https://operations.osmfoundation.org/policies/nominatim/ ' 'and may possibly cause 403 and 429 HTTP errors. ' 'Please specify a custom `user_agent` with ' '`Nominatim(user_agent="my-application")` or by ' 'overriding the default `user_agent`: ' '`geopy.geocoders.options.default_user_agent = "my-application"`.' % self.headers['User-Agent'] ) self.api = "%s://%s%s" % (self.scheme, self.domain, self.geocode_path) self.reverse_api = "%s://%s%s" % (self.scheme, self.domain, self.reverse_path) def _construct_url(self, base_api, params): """ Construct geocoding request url. The method can be overridden in Nominatim-based geocoders in order to extend URL parameters. :param str base_api: Geocoding function base address - self.api or self.reverse_api. :param dict params: Geocoding params. :return: string URL. """ return "?".join((base_api, urlencode(params))) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, limit=None, addressdetails=False, language=False, geometry=None, extratags=False, country_codes=None, viewbox=None, bounded=False, featuretype=None, namedetails=False ): """ Return a location point by address. :param query: The address, query or a structured query you wish to geocode. For a structured query, provide a dictionary whose keys are one of: `street`, `city`, `county`, `state`, `country`, or `postalcode`. For more information, see Nominatim's documentation for `structured requests`: https://nominatim.org/release-docs/develop/api/Search :type query: dict or str :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int limit: Maximum amount of results to return from Nominatim. Unless exactly_one is set to False, limit will always be 1. :param bool addressdetails: If you want in *Location.raw* to include address details such as house_number, city_district, postcode, etc (in a structured form) set it to True :param str language: Preferred language in which to return results. Either uses standard `RFC2616 `_ accept-language string or a simple comma-separated list of language codes. :param str geometry: If present, specifies whether the geocoding service should return the result's geometry in `wkt`, `svg`, `kml`, or `geojson` formats. This is available via the `raw` attribute on the returned :class:`geopy.location.Location` object. :param bool extratags: Include additional information in the result if available, e.g. wikipedia link, opening hours. :param country_codes: Limit search results to a specific country (or a list of countries). A country_code should be the ISO 3166-1alpha2 code, e.g. ``gb`` for the United Kingdom, ``de`` for Germany, etc. :type country_codes: str or list :type viewbox: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param viewbox: Prefer this area to find search results. By default this is treated as a hint, if you want to restrict results to this area, specify ``bounded=True`` as well. Example: ``[Point(22, 180), Point(-22, -180)]``. :param bool bounded: Restrict the results to only items contained within the bounding ``viewbox``. :param str featuretype: If present, restrict results to certain type of features. Allowed values: `country`, `state`, `city`, `settlement`. :param bool namedetails: If you want in *Location.raw* to include namedetails, set it to True. This will be a list of alternative names, including language variants, etc. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if isinstance(query, collections.abc.Mapping): params = { key: val for key, val in query.items() if key in self.structured_query_params } else: params = {'q': query} params.update({ 'format': 'json' }) if exactly_one: params['limit'] = 1 elif limit is not None: limit = int(limit) if limit < 1: raise ValueError("Limit cannot be less than 1") params['limit'] = limit if viewbox: params['viewbox'] = self._format_bounding_box( viewbox, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") if bounded: params['bounded'] = 1 if not country_codes: country_codes = [] if isinstance(country_codes, str): country_codes = [country_codes] if country_codes: params['countrycodes'] = ",".join(country_codes) if addressdetails: params['addressdetails'] = 1 if namedetails: params['namedetails'] = 1 if language: params['accept-language'] = language if extratags: params['extratags'] = True if geometry is not None: geometry = geometry.lower() if geometry == 'wkt': params['polygon_text'] = 1 elif geometry == 'svg': params['polygon_svg'] = 1 elif geometry == 'kml': params['polygon_kml'] = 1 elif geometry == 'geojson': params['polygon_geojson'] = 1 else: raise GeocoderQueryError( "Invalid geometry format. Must be one of: " "wkt, svg, kml, geojson." ) if featuretype: params['featuretype'] = featuretype url = self._construct_url(self.api, params) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=False, addressdetails=True, zoom=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str language: Preferred language in which to return results. Either uses standard `RFC2616 `_ accept-language string or a simple comma-separated list of language codes. :param bool addressdetails: Whether or not to include address details, such as city, county, state, etc. in *Location.raw* :param int zoom: Level of detail required for the address, an integer in range from 0 (country level) to 18 (building level), default is 18. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: lat, lon = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { 'lat': lat, 'lon': lon, 'format': 'json', } if language: params['accept-language'] = language params['addressdetails'] = 1 if addressdetails else 0 if zoom is not None: params['zoom'] = zoom url = self._construct_url(self.reverse_api, params) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_code(self, place): # Parse each resource. latitude = place.get('lat', None) longitude = place.get('lon', None) placename = place.get('display_name', None) if latitude is not None and longitude is not None: latitude = float(latitude) longitude = float(longitude) return Location(placename, (latitude, longitude), place) def _parse_json(self, places, exactly_one): if not places: return None if isinstance(places, collections.abc.Mapping) and 'error' in places: if places['error'] == 'Unable to geocode': # no results in reverse return None else: raise GeocoderQueryError(places['error']) if not isinstance(places, collections.abc.Sequence): places = [places] if exactly_one: return self._parse_code(places[0]) else: return [self._parse_code(place) for place in places] geopy-2.2.0/geopy/geocoders/opencage.py0000644000076500000240000001762114034400311020520 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.exc import GeocoderServiceError from geopy.geocoders.base import DEFAULT_SENTINEL, ERROR_CODE_MAP, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("OpenCage", ) class OpenCage(Geocoder): """Geocoder using the OpenCageData API. Documentation at: https://opencagedata.com/api .. versionchanged:: 2.2 Improved error handling by using the default errors map (e.g. to raise :class:`.exc.GeocoderQuotaExceeded` instead of :class:`.exc.GeocoderQueryError` for HTTP 402 error) """ api_path = '/geocode/v1/json' def __init__( self, api_key, *, domain='api.opencagedata.com', scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: The API key required by OpenCageData to perform geocoding requests. You can get your key here: https://opencagedata.com/ :param str domain: Currently it is ``'api.opencagedata.com'``, can be changed for testing purposes. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.api = '%s://%s%s' % (self.scheme, self.domain, self.api_path) def geocode( self, query, *, bounds=None, country=None, language=None, annotations=True, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :type bounds: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param bounds: Provides the geocoder with a hint to the region that the query resides in. This value will help the geocoder but will not restrict the possible results to the supplied region. The bounds parameter should be specified as 2 coordinate points -- corners of a bounding box. Example: ``[Point(22, 180), Point(-22, -180)]``. :param country: Restricts the results to the specified country or countries. The country code is a 2 character code as defined by the ISO 3166-1 Alpha 2 standard (e.g. ``fr``). Might be a Python list of strings. :type country: str or list :param str language: an IETF format language code (such as `es` for Spanish or pt-BR for Brazilian Portuguese); if this is omitted a code of `en` (English) will be assumed by the remote service. :param bool annotations: Enable `annotations `_ data, which can be accessed via :attr:`.Location.raw`. Set to False if you don't need it to gain a little performance win. .. versionadded:: 2.2 :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'key': self.api_key, 'q': query, } if not annotations: params['no_annotations'] = 1 if bounds: params['bounds'] = self._format_bounding_box( bounds, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") if language: params['language'] = language if not country: country = [] if isinstance(country, str): country = [country] if country: params['countrycode'] = ",".join(country) url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, language=None, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param str language: The language in which to return results. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'key': self.api_key, 'q': self._coerce_point_to_string(query), } if language: params['language'] = language url = "?".join((self.api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, page, exactly_one=True): '''Returns location, (latitude, longitude) from json feed.''' places = page.get('results', []) if not len(places): self._check_status(page.get('status')) return None def parse_place(place): '''Get the location, lat, lng from a single json place.''' location = place.get('formatted') latitude = place['geometry']['lat'] longitude = place['geometry']['lng'] return Location(location, (latitude, longitude), place) if exactly_one: return parse_place(places[0]) else: return [parse_place(place) for place in places] def _check_status(self, status): status_code = status['code'] message = status['message'] if status_code == 200: return # https://opencagedata.com/api#codes exc_cls = ERROR_CODE_MAP.get(status_code, GeocoderServiceError) raise exc_cls(message) geopy-2.2.0/geopy/geocoders/openmapquest.py0000644000076500000240000000500113717774171021476 0ustar kostyastaff00000000000000from geopy.geocoders.base import DEFAULT_SENTINEL from geopy.geocoders.nominatim import Nominatim __all__ = ("OpenMapQuest", ) class OpenMapQuest(Nominatim): """Geocoder using MapQuest Open Platform Web Services. Documentation at: https://developer.mapquest.com/documentation/open/ MapQuest provides two Geocoding APIs: - :class:`geopy.geocoders.OpenMapQuest` (this class) Nominatim-alike API which is based on Open data from OpenStreetMap. - :class:`geopy.geocoders.MapQuest` MapQuest's own API which is based on Licensed data. """ geocode_path = '/nominatim/v1/search' reverse_path = '/nominatim/v1/reverse' def __init__( self, api_key, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, domain='open.mapquestapi.com', scheme=None, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: API key provided by MapQuest, required. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str domain: Domain where the target Nominatim service is hosted. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( timeout=timeout, proxies=proxies, domain=domain, scheme=scheme, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key def _construct_url(self, base_api, params): """ Construct geocoding request url. Overridden. :param str base_api: Geocoding function base address - self.api or self.reverse_api. :param dict params: Geocoding params. :return: string URL. """ params['key'] = self.api_key return super()._construct_url(base_api, params) geopy-2.2.0/geopy/geocoders/osm.py0000644000076500000240000000045113717774171017557 0ustar kostyastaff00000000000000import warnings from geopy.geocoders.nominatim import Nominatim __all__ = ("Nominatim",) warnings.warn( "`geopy.geocoders.osm` module is deprecated. " "Use `geopy.geocoders.nominatim` instead. " "In geopy 3 this module will be removed.", DeprecationWarning, stacklevel=2, ) geopy-2.2.0/geopy/geocoders/pelias.py0000644000076500000240000001652014032207434020222 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Pelias", ) class Pelias(Geocoder): """Pelias geocoder. Documentation at: https://github.com/pelias/documentation See also :class:`geopy.geocoders.GeocodeEarth` which is a Pelias-based service provided by the developers of Pelias itself. """ geocode_path = '/v1/search' reverse_path = '/v1/reverse' def __init__( self, domain, api_key=None, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, scheme=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None # Make sure to synchronize the changes of this signature in the # inheriting classes (e.g. GeocodeEarth). ): """ :param str domain: Specify a domain for Pelias API. :param str api_key: Pelias API key, optional. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.domain = domain.strip('/') self.geocode_api = ( '%s://%s%s' % (self.scheme, self.domain, self.geocode_path) ) self.reverse_api = ( '%s://%s%s' % (self.scheme, self.domain, self.reverse_path) ) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, boundary_rect=None, country_bias=None, language=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :type boundary_rect: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :param boundary_rect: Coordinates to restrict search within. Example: ``[Point(22, 180), Point(-22, -180)]``. :param str country_bias: Bias results to this country (ISO alpha-3). :param str language: Preferred language in which to return results. Either uses standard `RFC2616 `_ accept-language string or a simple comma-separated list of language codes. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = {'text': query} if self.api_key: params.update({ 'api_key': self.api_key }) if boundary_rect: lon1, lat1, lon2, lat2 = self._format_bounding_box( boundary_rect, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s").split(',') params['boundary.rect.min_lon'] = lon1 params['boundary.rect.min_lat'] = lat1 params['boundary.rect.max_lon'] = lon2 params['boundary.rect.max_lat'] = lat2 if country_bias: params['boundary.country'] = country_bias if language: params["lang"] = language url = "?".join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode_api: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str language: Preferred language in which to return results. Either uses standard `RFC2616 `_ accept-language string or a simple comma-separated list of language codes. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: lat, lon = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { 'point.lat': lat, 'point.lon': lon, } if language: params['lang'] = language if self.api_key: params.update({ 'api_key': self.api_key }) url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_code(self, feature): # Parse each resource. latitude = feature.get('geometry', {}).get('coordinates', [])[1] longitude = feature.get('geometry', {}).get('coordinates', [])[0] placename = feature.get('properties', {}).get('name') return Location(placename, (latitude, longitude), feature) def _parse_json(self, response, exactly_one): if response is None: return None features = response['features'] if not len(features): return None if exactly_one: return self._parse_code(features[0]) else: return [self._parse_code(feature) for feature in features] geopy-2.2.0/geopy/geocoders/photon.py0000644000076500000240000002132214034400311020237 0ustar kostyastaff00000000000000import collections.abc from functools import partial from urllib.parse import urlencode from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Photon", ) class Photon(Geocoder): """Geocoder using Photon geocoding service (data based on OpenStreetMap and service provided by Komoot on https://photon.komoot.io). Documentation at: https://github.com/komoot/photon Photon/Komoot geocoder aims to let you `search as you type with OpenStreetMap`. No API Key is needed by this platform. .. versionchanged:: 2.2 Changed default domain from ``photon.komoot.de`` to ``photon.komoot.io``. """ geocode_path = '/api' reverse_path = '/reverse' def __init__( self, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, domain='photon.komoot.io', user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str domain: Should be the localized Photon domain to connect to. The default is ``'photon.komoot.io'``, but you can change it to a domain of your own. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.domain = domain.strip('/') self.api = "%s://%s%s" % (self.scheme, self.domain, self.geocode_path) self.reverse_api = "%s://%s%s" % (self.scheme, self.domain, self.reverse_path) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, location_bias=None, language=False, limit=None, osm_tag=None, bbox=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param location_bias: The coordinates to use as location bias. :type location_bias: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param str language: Preferred language in which to return results. :param int limit: Limit the number of returned results, defaults to no limit. :param osm_tag: The expression to filter (include/exclude) by key and/ or value, str as ``'key:value'`` or list/set of str if multiple filters are required as ``['key:!val', '!key', ':!value']``. :type osm_tag: str or list or set :param bbox: The bounding box of the viewport within which to bias geocode results more prominently. Example: ``[Point(22, 180), Point(-22, -180)]``. .. versionadded:: 2.2 :type bbox: list or tuple of 2 items of :class:`geopy.point.Point` or ``(latitude, longitude)`` or ``"%(latitude)s, %(longitude)s"``. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'q': query } if limit: params['limit'] = int(limit) if exactly_one: params['limit'] = 1 if language: params['lang'] = language if location_bias: try: lat, lon = self._coerce_point_to_string(location_bias).split(',') params['lon'] = lon params['lat'] = lat except ValueError: raise ValueError(("Location bias must be a" " coordinate pair or Point")) if bbox: params['bbox'] = self._format_bounding_box( bbox, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") if osm_tag: if isinstance(osm_tag, str): params['osm_tag'] = [osm_tag] else: if not isinstance(osm_tag, collections.abc.Iterable): raise ValueError( "osm_tag must be a string or " "an iterable of strings" ) params['osm_tag'] = list(osm_tag) url = "?".join((self.api, urlencode(params, doseq=True))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=False, limit=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str language: Preferred language in which to return results. :param int limit: Limit the number of returned results, defaults to no limit. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: lat, lon = self._coerce_point_to_string(query).split(',') except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { 'lat': lat, 'lon': lon, } if limit: params['limit'] = int(limit) if exactly_one: params['limit'] = 1 if language: params['lang'] = language url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, resources, exactly_one=True): """ Parse display name, latitude, and longitude from a JSON response. """ if not len(resources['features']): # pragma: no cover return None if exactly_one: return self._parse_resource(resources['features'][0]) else: return [self._parse_resource(resource) for resource in resources['features']] def _parse_resource(self, resource): # Return location and coordinates tuple from dict. name_elements = ['name', 'housenumber', 'street', 'postcode', 'street', 'city', 'state', 'country'] name = [resource['properties'].get(k) for k in name_elements if resource['properties'].get(k)] location = ', '.join(name) latitude = resource['geometry']['coordinates'][1] longitude = resource['geometry']['coordinates'][0] if latitude and longitude: latitude = float(latitude) longitude = float(longitude) return Location(location, (latitude, longitude), resource) geopy-2.2.0/geopy/geocoders/pickpoint.py0000644000076500000240000000433713717774171020770 0ustar kostyastaff00000000000000from geopy.geocoders.base import DEFAULT_SENTINEL from geopy.geocoders.nominatim import Nominatim __all__ = ("PickPoint",) class PickPoint(Nominatim): """PickPoint geocoder is a commercial version of Nominatim. Documentation at: https://pickpoint.io/api-reference """ geocode_path = '/v1/forward' reverse_path = '/v1/reverse' def __init__( self, api_key, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, domain='api.pickpoint.io', scheme=None, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: PickPoint API key obtained at https://pickpoint.io. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str domain: Domain where the target Nominatim service is hosted. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( timeout=timeout, proxies=proxies, domain=domain, scheme=scheme, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key def _construct_url(self, base_api, params): """ Construct geocoding request url. Overridden. :param str base_api: Geocoding function base address - self.api or self.reverse_api. :param dict params: Geocoding params. :return: string URL. """ params['key'] = self.api_key return super()._construct_url(base_api, params) geopy-2.2.0/geopy/geocoders/smartystreets.py0000644000076500000240000001120313717774172021710 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.adapters import AdapterHTTPError from geopy.exc import GeocoderQuotaExceeded from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("LiveAddress", ) class LiveAddress(Geocoder): """Geocoder using the LiveAddress API provided by SmartyStreets. Documentation at: https://smartystreets.com/docs/cloud/us-street-api """ geocode_path = '/street-address' def __init__( self, auth_id, auth_token, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str auth_id: Valid `Auth ID` from SmartyStreets. :param str auth_token: Valid `Auth Token` from SmartyStreets. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme='https', timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.auth_id = auth_id self.auth_token = auth_token domain = 'api.smartystreets.com' self.api = '%s://%s%s' % (self.scheme, domain, self.geocode_path) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, candidates=1 ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int candidates: An integer between 1 and 10 indicating the max number of candidate addresses to return if a valid address could be found. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if not (1 <= candidates <= 10): raise ValueError('candidates must be between 1 and 10') query = { 'auth-id': self.auth_id, 'auth-token': self.auth_token, 'street': query, 'candidates': candidates, } url = '{url}?{query}'.format(url=self.api, query=urlencode(query)) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _geocoder_exception_handler(self, error): search = "no active subscriptions found" if isinstance(error, AdapterHTTPError): if search in str(error).lower(): raise GeocoderQuotaExceeded(str(error)) from error if search in (error.text or "").lower(): raise GeocoderQuotaExceeded(error.text) from error def _parse_json(self, response, exactly_one=True): """ Parse responses as JSON objects. """ if not len(response): return None if exactly_one: return self._format_structured_address(response[0]) else: return [self._format_structured_address(c) for c in response] def _format_structured_address(self, address): """ Pretty-print address and return lat, lon tuple. """ latitude = address['metadata'].get('latitude') longitude = address['metadata'].get('longitude') return Location( ", ".join((address['delivery_line_1'], address['last_line'])), (latitude, longitude) if latitude and longitude else None, address ) geopy-2.2.0/geopy/geocoders/tomtom.py0000644000076500000240000002001513717774171020276 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import quote, urlencode from geopy.adapters import AdapterHTTPError from geopy.exc import GeocoderQuotaExceeded from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("TomTom", ) class TomTom(Geocoder): """TomTom geocoder. Documentation at: https://developer.tomtom.com/search-api/search-api-documentation """ geocode_path = '/search/2/geocode/%(query)s.json' reverse_path = '/search/2/reverseGeocode/%(position)s.json' def __init__( self, api_key, *, scheme=None, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None, domain='api.tomtom.com' ): """ :param str api_key: TomTom API key. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 :param str domain: Domain where the target TomTom service is hosted. """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key self.api = "%s://%s%s" % (self.scheme, domain, self.geocode_path) self.api_reverse = "%s://%s%s" % (self.scheme, domain, self.reverse_path) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, limit=None, typeahead=False, language=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param int limit: Maximum amount of results to return from the service. Unless exactly_one is set to False, limit will always be 1. :param bool typeahead: If the "typeahead" flag is set, the query will be interpreted as a partial input and the search will enter predictive mode. :param str language: Language in which search results should be returned. When data in specified language is not available for a specific field, default language is used. List of supported languages (case-insensitive): https://developer.tomtom.com/online-search/online-search-documentation/supported-languages :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = self._geocode_params(query) params['typeahead'] = self._boolean_value(typeahead) if limit: params['limit'] = str(int(limit)) if exactly_one: params['limit'] = '1' if language: params['language'] = language quoted_query = quote(query.encode('utf-8')) url = "?".join((self.api % dict(query=quoted_query), urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, language=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str language: Language in which search results should be returned. When data in specified language is not available for a specific field, default language is used. List of supported languages (case-insensitive): https://developer.tomtom.com/online-search/online-search-documentation/supported-languages :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ position = self._coerce_point_to_string(query) params = self._reverse_params(position) if language: params['language'] = language quoted_position = quote(position.encode('utf-8')) url = "?".join((self.api_reverse % dict(position=quoted_position), urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_reverse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _boolean_value(self, bool_value): return 'true' if bool_value else 'false' def _geocode_params(self, formatted_query): return { 'key': self.api_key, } def _reverse_params(self, position): return { 'key': self.api_key, } def _parse_json(self, resources, exactly_one): if not resources or not resources['results']: return None if exactly_one: return self._parse_search_result(resources['results'][0]) else: return [self._parse_search_result(result) for result in resources['results']] def _parse_search_result(self, result): latitude = result['position']['lat'] longitude = result['position']['lon'] return Location(result['address']['freeformAddress'], (latitude, longitude), result) def _parse_reverse_json(self, resources, exactly_one): if not resources or not resources['addresses']: return None if exactly_one: return self._parse_reverse_result(resources['addresses'][0]) else: return [self._parse_reverse_result(result) for result in resources['addresses']] def _parse_reverse_result(self, result): latitude, longitude = result['position'].split(',') return Location(result['address']['freeformAddress'], (latitude, longitude), result) def _geocoder_exception_handler(self, error): if not isinstance(error, AdapterHTTPError): return if error.status_code is None or error.text is None: return if error.status_code >= 400 and "Developer Over Qps" in error.text: raise GeocoderQuotaExceeded("Developer Over Qps") from error geopy-2.2.0/geopy/geocoders/what3words.py0000644000076500000240000003361214034375231021056 0ustar kostyastaff00000000000000import re from functools import partial from urllib.parse import urlencode from geopy import exc from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("What3Words", "What3WordsV3") _MULTIPLE_WORD_RE = re.compile( r"[^\W\d\_]+\.{1,1}[^\W\d\_]+\.{1,1}[^\W\d\_]+$", re.U ) def _check_query(query): """ Check query validity with regex """ if not _MULTIPLE_WORD_RE.match(query): return False else: return True class What3Words(Geocoder): """What3Words geocoder using the legacy V2 API. Documentation at: https://docs.what3words.com/api/v2/ .. attention:: Consider using :class:`.What3WordsV3` instead. """ geocode_path = '/v2/forward' reverse_path = '/v2/reverse' def __init__( self, api_key, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: Key provided by What3Words (https://accounts.what3words.com/register). :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme='https', timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key domain = 'api.what3words.com' self.geocode_api = '%s://%s%s' % (self.scheme, domain, self.geocode_path) self.reverse_api = '%s://%s%s' % (self.scheme, domain, self.reverse_path) def geocode( self, query, *, lang='en', exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point for a `3 words` query. If the `3 words` address doesn't exist, a :class:`geopy.exc.GeocoderQueryError` exception will be thrown. :param str query: The 3-word address you wish to geocode. :param str lang: two character language code as supported by the API (https://docs.what3words.com/api/v2/#lang). :param bool exactly_one: Return one result or a list of results, if available. Due to the address scheme there is always exactly one result for each `3 words` address, so this parameter is rather useless for this geocoder. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if not _check_query(query): raise exc.GeocoderQueryError( "Search string must be 'word.word.word'" ) params = { 'addr': query, 'lang': lang.lower(), 'key': self.api_key, } url = "?".join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, resources, exactly_one=True): """ Parse type, words, latitude, and longitude and language from a JSON response. """ code = resources['status'].get('code') if code: # https://docs.what3words.com/api/v2/#errors exc_msg = "Error returned by What3Words: %s" % resources['status']['message'] if code == 401: raise exc.GeocoderAuthenticationFailure(exc_msg) raise exc.GeocoderQueryError(exc_msg) def parse_resource(resource): """ Parse record. """ if 'geometry' in resource: words = resource['words'] position = resource['geometry'] latitude, longitude = position['lat'], position['lng'] if latitude and longitude: latitude = float(latitude) longitude = float(longitude) return Location(words, (latitude, longitude), resource) else: raise exc.GeocoderParseError('Error parsing result.') location = parse_resource(resources) if exactly_one: return location else: return [location] def reverse( self, query, *, lang='en', exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a `3 words` address by location point. Each point on surface has a `3 words` address, so there's always a non-empty response. :param query: The coordinates for which you wish to obtain the 3 word address. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param str lang: two character language code as supported by the API (https://docs.what3words.com/api/v2/#lang). :param bool exactly_one: Return one result or a list of results, if available. Due to the address scheme there is always exactly one result for each `3 words` address, so this parameter is rather useless for this geocoder. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ lang = lang.lower() params = { 'coords': self._coerce_point_to_string(query), 'lang': lang.lower(), 'key': self.api_key, } url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_reverse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_reverse_json(self, resources, exactly_one=True): """ Parses a location from a single-result reverse API call. """ return self._parse_json(resources, exactly_one) class What3WordsV3(Geocoder): """What3Words geocoder using the V3 API. Documentation at: https://developer.what3words.com/public-api/docs .. versionadded:: 2.2 """ geocode_path = '/v3/convert-to-coordinates' reverse_path = '/v3/convert-to-3wa' def __init__( self, api_key, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: Key provided by What3Words (https://accounts.what3words.com/register). :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. """ super().__init__( scheme='https', timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key domain = 'api.what3words.com' self.geocode_api = '%s://%s%s' % (self.scheme, domain, self.geocode_path) self.reverse_api = '%s://%s%s' % (self.scheme, domain, self.reverse_path) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a location point for a `3 words` query. If the `3 words` address doesn't exist, a :class:`geopy.exc.GeocoderQueryError` exception will be thrown. :param str query: The 3-word address you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. Due to the address scheme there is always exactly one result for each `3 words` address, so this parameter is rather useless for this geocoder. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ if not _check_query(query): raise exc.GeocoderQueryError( "Search string must be 'word.word.word'" ) params = { 'words': query, 'key': self.api_key, } url = "?".join((self.geocode_api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, resources, exactly_one=True): """ Parse type, words, latitude, and longitude and language from a JSON response. """ error = resources.get('error') if error is not None: # https://developer.what3words.com/public-api/docs#error-handling exc_msg = "Error returned by What3Words: %s" % resources["error"]["message"] exc_code = error.get('code') if exc_code in ['MissingKey', 'InvalidKey']: raise exc.GeocoderAuthenticationFailure(exc_msg) raise exc.GeocoderQueryError(exc_msg) def parse_resource(resource): """ Parse record. """ if 'coordinates' in resource: words = resource['words'] position = resource['coordinates'] latitude, longitude = position['lat'], position['lng'] if latitude and longitude: latitude = float(latitude) longitude = float(longitude) return Location(words, (latitude, longitude), resource) else: raise exc.GeocoderParseError('Error parsing result.') location = parse_resource(resources) if exactly_one: return location else: return [location] def reverse( self, query, *, lang='en', exactly_one=True, timeout=DEFAULT_SENTINEL ): """ Return a `3 words` address by location point. Each point on surface has a `3 words` address, so there's always a non-empty response. :param query: The coordinates for which you wish to obtain the 3 word address. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param str lang: two character language code as supported by the API (https://developer.what3words.com/public-api/docs#available-languages). :param bool exactly_one: Return one result or a list of results, if available. Due to the address scheme there is always exactly one result for each `3 words` address, so this parameter is rather useless for this geocoder. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :rtype: :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ lang = lang.lower() params = { 'coordinates': self._coerce_point_to_string(query), 'language': lang.lower(), 'key': self.api_key, } url = "?".join((self.reverse_api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_reverse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_reverse_json(self, resources, exactly_one=True): """ Parses a location from a single-result reverse API call. """ return self._parse_json(resources, exactly_one) geopy-2.2.0/geopy/geocoders/yandex.py0000644000076500000240000001606514032167233020243 0ustar kostyastaff00000000000000from functools import partial from urllib.parse import urlencode from geopy.exc import GeocoderParseError, GeocoderServiceError from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder from geopy.location import Location from geopy.util import logger __all__ = ("Yandex", ) class Yandex(Geocoder): """Yandex geocoder. Documentation at: https://tech.yandex.com/maps/doc/geocoder/desc/concepts/input_params-docpage/ """ api_path = '/1.x/' def __init__( self, api_key, *, timeout=DEFAULT_SENTINEL, proxies=DEFAULT_SENTINEL, user_agent=None, scheme=None, ssl_context=DEFAULT_SENTINEL, adapter_factory=None ): """ :param str api_key: Yandex API key, mandatory. The key can be created at https://developer.tech.yandex.ru/ :param int timeout: See :attr:`geopy.geocoders.options.default_timeout`. :param dict proxies: See :attr:`geopy.geocoders.options.default_proxies`. :param str user_agent: See :attr:`geopy.geocoders.options.default_user_agent`. :param str scheme: See :attr:`geopy.geocoders.options.default_scheme`. :type ssl_context: :class:`ssl.SSLContext` :param ssl_context: See :attr:`geopy.geocoders.options.default_ssl_context`. :param callable adapter_factory: See :attr:`geopy.geocoders.options.default_adapter_factory`. .. versionadded:: 2.0 """ super().__init__( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=adapter_factory, ) self.api_key = api_key domain = 'geocode-maps.yandex.ru' self.api = '%s://%s%s' % (self.scheme, domain, self.api_path) def geocode( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, lang=None ): """ Return a location point by address. :param str query: The address or query you wish to geocode. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str lang: Language of the response and regional settings of the map. List of supported values: - ``tr_TR`` -- Turkish (only for maps of Turkey); - ``en_RU`` -- response in English, Russian map features; - ``en_US`` -- response in English, American map features; - ``ru_RU`` -- Russian (default); - ``uk_UA`` -- Ukrainian; - ``be_BY`` -- Belarusian. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ params = { 'geocode': query, 'format': 'json' } params['apikey'] = self.api_key if lang: params['lang'] = lang if exactly_one: params['results'] = 1 url = "?".join((self.api, urlencode(params))) logger.debug("%s.geocode: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def reverse( self, query, *, exactly_one=True, timeout=DEFAULT_SENTINEL, kind=None, lang=None ): """ Return an address by location point. :param query: The coordinates for which you wish to obtain the closest human-readable addresses. :type query: :class:`geopy.point.Point`, list or tuple of ``(latitude, longitude)``, or string as ``"%(latitude)s, %(longitude)s"``. :param bool exactly_one: Return one result or a list of results, if available. :param int timeout: Time, in seconds, to wait for the geocoding service to respond before raising a :class:`geopy.exc.GeocoderTimedOut` exception. Set this only if you wish to override, on this call only, the value set during the geocoder's initialization. :param str kind: Type of toponym. Allowed values: `house`, `street`, `metro`, `district`, `locality`. :param str lang: Language of the response and regional settings of the map. List of supported values: - ``tr_TR`` -- Turkish (only for maps of Turkey); - ``en_RU`` -- response in English, Russian map features; - ``en_US`` -- response in English, American map features; - ``ru_RU`` -- Russian (default); - ``uk_UA`` -- Ukrainian; - ``be_BY`` -- Belarusian. :rtype: ``None``, :class:`geopy.location.Location` or a list of them, if ``exactly_one=False``. """ try: point = self._coerce_point_to_string(query, "%(lon)s,%(lat)s") except ValueError: raise ValueError("Must be a coordinate pair or Point") params = { 'geocode': point, 'format': 'json' } params['apikey'] = self.api_key if lang: params['lang'] = lang if kind: params['kind'] = kind url = "?".join((self.api, urlencode(params))) logger.debug("%s.reverse: %s", self.__class__.__name__, url) callback = partial(self._parse_json, exactly_one=exactly_one) return self._call_geocoder(url, callback, timeout=timeout) def _parse_json(self, doc, exactly_one): """ Parse JSON response body. """ if doc.get('error'): raise GeocoderServiceError(doc['error']['message']) try: places = doc['response']['GeoObjectCollection']['featureMember'] except KeyError: raise GeocoderParseError('Failed to parse server response') def parse_code(place): """ Parse each record. """ try: place = place['GeoObject'] except KeyError: raise GeocoderParseError('Failed to parse server response') longitude, latitude = [ float(_) for _ in place['Point']['pos'].split(' ') ] name_elements = ['name', 'description'] location = ', '.join([place[k] for k in name_elements if place.get(k)]) return Location(location, (latitude, longitude), place) if exactly_one: try: return parse_code(places[0]) except IndexError: return None else: return [parse_code(place) for place in places] geopy-2.2.0/geopy/location.py0000644000076500000240000000673213700135050016602 0ustar kostyastaff00000000000000import collections.abc from geopy.point import Point def _location_tuple(location): return location._address, (location._point[0], location._point[1]) class Location: """ Contains a parsed geocoder response. Can be iterated over as ``(location, (latitude, longitude (?P[NS])?[ ]* (?P[+-]?%(FLOAT)s)(?:[%(DEGREE)sD\*\u00B0\s][ ]* (?:(?P%(FLOAT)s)[%(PRIME)s'm][ ]*)? (?:(?P%(FLOAT)s)[%(DOUBLE_PRIME)s"s][ ]*)? )?(?P[NS])?) %(SEP)s (?P (?P[EW])?[ ]* (?P[+-]?%(FLOAT)s)(?:[%(DEGREE)sD\*\u00B0\s][ ]* (?:(?P%(FLOAT)s)[%(PRIME)s'm][ ]*)? (?:(?P%(FLOAT)s)[%(DOUBLE_PRIME)s"s][ ]*)? )?(?P[EW])?)(?: %(SEP)s (?P (?P[+-]?%(FLOAT)s)[ ]* (?Pkm|m|mi|ft|nm|nmi)))? \s*$ """ % { "FLOAT": r'\d+(?:\.\d+)?', "DEGREE": DEGREE, "PRIME": PRIME, "DOUBLE_PRIME": DOUBLE_PRIME, "SEP": r'\s*[,;/\s]\s*', }, re.VERBOSE | re.UNICODE) def _normalize_angle(x, limit): """ Normalize angle `x` to be within `[-limit; limit)` range. """ double_limit = limit * 2.0 modulo = fmod(x, double_limit) or 0.0 # `or 0` is to turn -0 to +0. if modulo < -limit: return modulo + double_limit if modulo >= limit: return modulo - double_limit return modulo def _normalize_coordinates(latitude, longitude, altitude): latitude = float(latitude or 0.0) longitude = float(longitude or 0.0) altitude = float(altitude or 0.0) is_all_finite = all(isfinite(x) for x in (latitude, longitude, altitude)) if not is_all_finite: raise ValueError('Point coordinates must be finite. %r has been passed ' 'as coordinates.' % ((latitude, longitude, altitude),)) if abs(latitude) > 90: warnings.warn('Latitude normalization has been prohibited in the newer ' 'versions of geopy, because the normalized value happened ' 'to be on a different pole, which is probably not what was ' 'meant. If you pass coordinates as positional args, ' 'please make sure that the order is ' '(latitude, longitude) or (y, x) in Cartesian terms.', UserWarning, stacklevel=3) raise ValueError('Latitude must be in the [-90; 90] range.') if abs(longitude) > 180: # Longitude normalization is pretty straightforward and doesn't seem # to be error-prone, so there's nothing to complain about. longitude = _normalize_angle(longitude, 180.0) return latitude, longitude, altitude class Point: """ A geodetic point with latitude, longitude, and altitude. Latitude and longitude are floating point values in degrees. Altitude is a floating point value in kilometers. The reference level is never considered and is thus application dependent, so be consistent! The default for all values is 0. Points can be created in a number of ways... With latitude, longitude, and altitude:: >>> p1 = Point(41.5, -81, 0) >>> p2 = Point(latitude=41.5, longitude=-81) With a sequence of 2 to 3 values (latitude, longitude, altitude):: >>> p1 = Point([41.5, -81, 0]) >>> p2 = Point((41.5, -81)) Copy another `Point` instance:: >>> p2 = Point(p1) >>> p2 == p1 True >>> p2 is p1 False Give a string containing at least latitude and longitude:: >>> p = Point('41.5,-81.0') >>> p = Point('+41.5 -81.0') >>> p = Point('41.5 N -81.0 W') >>> p = Point('-41.5 S, 81.0 E, 2.5km') >>> p = Point('23 26m 22s N 23 27m 30s E 21.0mi') >>> p = Point('''3 26' 22" N 23 27' 30" E''') Point values can be accessed by name or by index:: >>> p = Point(41.5, -81.0, 0) >>> p.latitude == p[0] True >>> p.longitude == p[1] True >>> p.altitude == p[2] True When unpacking (or iterating), a ``(latitude, longitude, altitude)`` tuple is returned:: >>> latitude, longitude, altitude = p Textual representations:: >>> p = Point(41.5, -81.0, 12.3) >>> str(p) # same as `p.format()` '41 30m 0s N, 81 0m 0s W, 12.3km' >>> p.format_unicode() '41° 30′ 0″ N, 81° 0′ 0″ W, 12.3km' >>> repr(p) 'Point(41.5, -81.0, 12.3)' >>> repr(tuple(p)) '(41.5, -81.0, 12.3)' """ __slots__ = ("latitude", "longitude", "altitude") POINT_PATTERN = POINT_PATTERN def __new__(cls, latitude=None, longitude=None, altitude=None): """ :param float latitude: Latitude of point. :param float longitude: Longitude of point. :param float altitude: Altitude of point. """ single_arg = latitude is not None and longitude is None and altitude is None if single_arg and not isinstance(latitude, util.NUMBER_TYPES): arg = latitude if isinstance(arg, Point): return cls.from_point(arg) elif isinstance(arg, str): return cls.from_string(arg) else: try: seq = iter(arg) except TypeError: raise TypeError( "Failed to create Point instance from %r." % (arg,) ) else: return cls.from_sequence(seq) if single_arg: raise ValueError( 'A single number has been passed to the Point ' 'constructor. This is probably a mistake, because ' 'constructing a Point with just a latitude ' 'seems senseless. If this is exactly what was ' 'meant, then pass the zero longitude explicitly ' 'to get rid of this error.' ) latitude, longitude, altitude = \ _normalize_coordinates(latitude, longitude, altitude) self = super().__new__(cls) self.latitude = latitude self.longitude = longitude self.altitude = altitude return self def __getitem__(self, index): return tuple(self)[index] # tuple handles slices def __setitem__(self, index, value): point = list(self) point[index] = value # list handles slices self.latitude, self.longitude, self.altitude = \ _normalize_coordinates(*point) def __iter__(self): return iter((self.latitude, self.longitude, self.altitude)) def __getstate__(self): return tuple(self) def __setstate__(self, state): self.latitude, self.longitude, self.altitude = state def __repr__(self): return "Point(%r, %r, %r)" % tuple(self) def format(self, altitude=None, deg_char='', min_char='m', sec_char='s'): """ Format decimal degrees (DD) to degrees minutes seconds (DMS):: >>> p = Point(41.5, -81.0, 12.3) >>> p.format() '41 30m 0s N, 81 0m 0s W, 12.3km' >>> p = Point(41.5, 0, 0) >>> p.format() '41 30m 0s N, 0 0m 0s E' See also :meth:`.format_unicode`. :param bool altitude: Whether to include ``altitude`` value. By default it is automatically included if it is non-zero. """ latitude = "%s %s" % ( format_degrees(abs(self.latitude), symbols={ 'deg': deg_char, 'arcmin': min_char, 'arcsec': sec_char }), self.latitude >= 0 and 'N' or 'S' ) longitude = "%s %s" % ( format_degrees(abs(self.longitude), symbols={ 'deg': deg_char, 'arcmin': min_char, 'arcsec': sec_char }), self.longitude >= 0 and 'E' or 'W' ) coordinates = [latitude, longitude] if altitude is None: altitude = bool(self.altitude) if altitude: if not isinstance(altitude, str): altitude = 'km' coordinates.append(self.format_altitude(altitude)) return ", ".join(coordinates) def format_unicode(self, altitude=None): """ :meth:`.format` with pretty unicode chars for degrees, minutes and seconds:: >>> p = Point(41.5, -81.0, 12.3) >>> p.format_unicode() '41° 30′ 0″ N, 81° 0′ 0″ W, 12.3km' :param bool altitude: Whether to include ``altitude`` value. By default it is automatically included if it is non-zero. """ return self.format( altitude, DEGREE, PRIME, DOUBLE_PRIME ) def format_decimal(self, altitude=None): """ Format decimal degrees with altitude:: >>> p = Point(41.5, -81.0, 12.3) >>> p.format_decimal() '41.5, -81.0, 12.3km' >>> p = Point(41.5, 0, 0) >>> p.format_decimal() '41.5, 0.0' :param bool altitude: Whether to include ``altitude`` value. By default it is automatically included if it is non-zero. """ coordinates = [str(self.latitude), str(self.longitude)] if altitude is None: altitude = bool(self.altitude) if altitude: if not isinstance(altitude, str): altitude = 'km' coordinates.append(self.format_altitude(altitude)) return ", ".join(coordinates) def format_altitude(self, unit='km'): """ Format altitude with unit:: >>> p = Point(41.5, -81.0, 12.3) >>> p.format_altitude() '12.3km' >>> p = Point(41.5, -81.0, 0) >>> p.format_altitude() '0.0km' :param str unit: Resulting altitude unit. Supported units are listed in :meth:`.from_string` doc. """ return format_distance(self.altitude, unit=unit) def __str__(self): return self.format() def __eq__(self, other): if not isinstance(other, collections.abc.Iterable): return NotImplemented return tuple(self) == tuple(other) def __ne__(self, other): return not (self == other) @classmethod def parse_degrees(cls, degrees, arcminutes, arcseconds, direction=None): """ Convert degrees, minutes, seconds and direction (N, S, E, W) to a single degrees number. :rtype: float """ degrees = float(degrees) negative = degrees < 0 arcminutes = float(arcminutes) arcseconds = float(arcseconds) if arcminutes or arcseconds: more = units.degrees(arcminutes=arcminutes, arcseconds=arcseconds) if negative: degrees -= more else: degrees += more if direction in [None, 'N', 'E']: return degrees elif direction in ['S', 'W']: return -degrees else: raise ValueError("Invalid direction! Should be one of [NSEW].") @classmethod def parse_altitude(cls, distance, unit): """ Parse altitude managing units conversion:: >>> Point.parse_altitude(712, 'm') 0.712 >>> Point.parse_altitude(712, 'km') 712.0 >>> Point.parse_altitude(712, 'mi') 1145.852928 :param float distance: Numeric value of altitude. :param str unit: ``distance`` unit. Supported units are listed in :meth:`.from_string` doc. """ if distance is not None: distance = float(distance) CONVERTERS = { 'km': lambda d: d, 'm': lambda d: units.kilometers(meters=d), 'mi': lambda d: units.kilometers(miles=d), 'ft': lambda d: units.kilometers(feet=d), 'nm': lambda d: units.kilometers(nautical=d), 'nmi': lambda d: units.kilometers(nautical=d) } try: return CONVERTERS[unit](distance) except KeyError: raise NotImplementedError( 'Bad distance unit specified, valid are: %r' % CONVERTERS.keys() ) else: return distance @classmethod def from_string(cls, string): """ Create and return a ``Point`` instance from a string containing latitude and longitude, and optionally, altitude. Latitude and longitude must be in degrees and may be in decimal form or indicate arcminutes and arcseconds (labeled with Unicode prime and double prime, ASCII quote and double quote or 'm' and 's'). The degree symbol is optional and may be included after the decimal places (in decimal form) and before the arcminutes and arcseconds otherwise. Coordinates given from south and west (indicated by S and W suffixes) will be converted to north and east by switching their signs. If no (or partial) cardinal directions are given, north and east are the assumed directions. Latitude and longitude must be separated by at least whitespace, a comma, or a semicolon (each with optional surrounding whitespace). Altitude, if supplied, must be a decimal number with given units. The following unit abbrevations (case-insensitive) are supported: - ``km`` (kilometers) - ``m`` (meters) - ``mi`` (miles) - ``ft`` (feet) - ``nm``, ``nmi`` (nautical miles) Some example strings that will work include: - ``41.5;-81.0`` - ``41.5,-81.0`` - ``41.5 -81.0`` - ``41.5 N -81.0 W`` - ``-41.5 S;81.0 E`` - ``23 26m 22s N 23 27m 30s E`` - ``23 26' 22" N 23 27' 30" E`` - ``UT: N 39°20' 0'' / W 74°35' 0''`` """ match = re.match(cls.POINT_PATTERN, re.sub(r"''", r'"', string)) if match: latitude_direction = None if match.group("latitude_direction_front"): latitude_direction = match.group("latitude_direction_front") elif match.group("latitude_direction_back"): latitude_direction = match.group("latitude_direction_back") longitude_direction = None if match.group("longitude_direction_front"): longitude_direction = match.group("longitude_direction_front") elif match.group("longitude_direction_back"): longitude_direction = match.group("longitude_direction_back") latitude = cls.parse_degrees( match.group('latitude_degrees') or 0.0, match.group('latitude_arcminutes') or 0.0, match.group('latitude_arcseconds') or 0.0, latitude_direction ) longitude = cls.parse_degrees( match.group('longitude_degrees') or 0.0, match.group('longitude_arcminutes') or 0.0, match.group('longitude_arcseconds') or 0.0, longitude_direction ) altitude = cls.parse_altitude( match.group('altitude_distance'), match.group('altitude_units') ) return cls(latitude, longitude, altitude) else: raise ValueError( "Failed to create Point instance from string: unknown format." ) @classmethod def from_sequence(cls, seq): """ Create and return a new ``Point`` instance from any iterable with 2 to 3 elements. The elements, if present, must be latitude, longitude, and altitude, respectively. """ args = tuple(islice(seq, 4)) if len(args) > 3: raise ValueError('When creating a Point from sequence, it ' 'must not have more than 3 items.') return cls(*args) @classmethod def from_point(cls, point): """ Create and return a new ``Point`` instance from another ``Point`` instance. """ return cls(point.latitude, point.longitude, point.altitude) geopy-2.2.0/geopy/timezone.py0000644000076500000240000000464513717774171016652 0ustar kostyastaff00000000000000from geopy.exc import GeocoderParseError try: import pytz pytz_available = True except ImportError: pytz_available = False __all__ = ( "Timezone", ) def ensure_pytz_is_installed(): if not pytz_available: raise ImportError( 'pytz must be installed in order to locate timezones. ' 'If geopy has been installed with `pip`, then pytz can be ' 'installed with `pip install "geopy[timezone]"`.' ) def from_timezone_name(timezone_name, raw): ensure_pytz_is_installed() try: pytz_timezone = pytz.timezone(timezone_name) except pytz.UnknownTimeZoneError: raise GeocoderParseError( "pytz could not parse the timezone identifier (%s) " "returned by the service." % timezone_name ) except KeyError: raise GeocoderParseError( "geopy could not find a timezone in this response: %s" % raw ) return Timezone(pytz_timezone, raw) def from_fixed_gmt_offset(gmt_offset_hours, raw): ensure_pytz_is_installed() pytz_timezone = pytz.FixedOffset(gmt_offset_hours * 60) return Timezone(pytz_timezone, raw) class Timezone: """ Contains a parsed response for a timezone request, which is implemented in few geocoders which provide such lookups. """ __slots__ = ("_pytz_timezone", "_raw") def __init__(self, pytz_timezone, raw): self._pytz_timezone = pytz_timezone self._raw = raw @property def pytz_timezone(self): """ pytz timezone instance. :rtype: :class:`pytz.tzinfo.BaseTzInfo` """ return self._pytz_timezone @property def raw(self): """ Timezone's raw, unparsed geocoder response. For details on this, consult the service's documentation. :rtype: dict """ return self._raw def __str__(self): return str(self._pytz_timezone) def __repr__(self): return "Timezone(%s)" % repr(self.pytz_timezone) def __getstate__(self): return self._pytz_timezone, self._raw def __setstate__(self, state): self._pytz_timezone, self._raw = state def __eq__(self, other): return ( isinstance(other, Timezone) and self._pytz_timezone == other._pytz_timezone and self.raw == other.raw ) def __ne__(self, other): return not (self == other) geopy-2.2.0/geopy/units.py0000644000076500000240000000560613717774171016160 0ustar kostyastaff00000000000000"""``geopy.units`` module provides utility functions for performing angle and distance unit conversions. Some shortly named aliases are provided for convenience (e.g. :func:`.km` is an alias for :func:`.kilometers`). """ import math # Angles def degrees(radians=0, arcminutes=0, arcseconds=0): """ Convert angle to degrees. """ deg = 0. if radians: deg = math.degrees(radians) if arcminutes: deg += arcminutes / arcmin(degrees=1.) if arcseconds: deg += arcseconds / arcsec(degrees=1.) return deg def radians(degrees=0, arcminutes=0, arcseconds=0): """ Convert angle to radians. """ if arcminutes: degrees += arcminutes / arcmin(degrees=1.) if arcseconds: degrees += arcseconds / arcsec(degrees=1.) return math.radians(degrees) def arcminutes(degrees=0, radians=0, arcseconds=0): """ Convert angle to arcminutes. """ if radians: degrees += math.degrees(radians) if arcseconds: degrees += arcseconds / arcsec(degrees=1.) return degrees * 60. def arcseconds(degrees=0, radians=0, arcminutes=0): """ Convert angle to arcseconds. """ if radians: degrees += math.degrees(radians) if arcminutes: degrees += arcminutes / arcmin(degrees=1.) return degrees * 3600. # Lengths def kilometers(meters=0, miles=0, feet=0, nautical=0): """ Convert distance to kilometers. """ ret = 0. if meters: ret += meters / 1000. if feet: ret += feet / ft(1.) if nautical: ret += nautical / nm(1.) ret += miles * 1.609344 return ret def meters(kilometers=0, miles=0, feet=0, nautical=0): """ Convert distance to meters. """ return (kilometers + km(nautical=nautical, miles=miles, feet=feet)) * 1000 def miles(kilometers=0, meters=0, feet=0, nautical=0): """ Convert distance to miles. """ ret = 0. if nautical: kilometers += nautical / nm(1.) if feet: kilometers += feet / ft(1.) if meters: kilometers += meters / 1000. ret += kilometers / 1.609344 return ret def feet(kilometers=0, meters=0, miles=0, nautical=0): """ Convert distance to feet. """ ret = 0. if nautical: kilometers += nautical / nm(1.) if meters: kilometers += meters / 1000. if kilometers: miles += mi(kilometers=kilometers) ret += miles * 5280 return ret def nautical(kilometers=0, meters=0, miles=0, feet=0): """ Convert distance to nautical miles. """ ret = 0. if feet: kilometers += feet / ft(1.) if miles: kilometers += km(miles=miles) if meters: kilometers += meters / 1000. ret += kilometers / 1.852 return ret # Compatible names rad = radians arcmin = arcminutes arcsec = arcseconds km = kilometers m = meters mi = miles ft = feet nm = nautical geopy-2.2.0/geopy/util.py0000644000076500000240000000101614072556610015751 0ustar kostyastaff00000000000000import logging from decimal import Decimal NUMBER_TYPES = (int, float, Decimal) __version__ = "2.2.0" __version_info__ = (2, 2, 0) logger = logging.getLogger('geopy') def pairwise(seq): """ Pair an iterable, e.g., (1, 2, 3, 4) -> ((1, 2), (2, 3), (3, 4)) """ for i in range(0, len(seq) - 1): yield (seq[i], seq[i + 1]) def join_filter(sep, seq, pred=bool): """ Join with a filter. """ return sep.join([str(i) for i in seq if pred(i)]) def get_version(): return __version__ geopy-2.2.0/geopy.egg-info/0000755000076500000240000000000014072560236016115 5ustar kostyastaff00000000000000geopy-2.2.0/geopy.egg-info/PKG-INFO0000644000076500000240000001437114072560236017220 0ustar kostyastaff00000000000000Metadata-Version: 2.1 Name: geopy Version: 2.2.0 Summary: Python Geocoding Toolbox Home-page: https://github.com/geopy/geopy Maintainer: Kostya Esmukov Maintainer-email: kostya@esmukov.ru License: MIT Download-URL: https://github.com/geopy/geopy/archive/2.2.0.tar.gz Description: geopy ===== .. image:: https://img.shields.io/pypi/v/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: Latest Version .. image:: https://img.shields.io/github/workflow/status/geopy/geopy/CI?style=flat-square :target: https://github.com/geopy/geopy/actions :alt: Build Status .. image:: https://img.shields.io/github/license/geopy/geopy.svg?style=flat-square :target: https://pypi.python.org/pypi/geopy/ :alt: License geopy is a Python client for several popular geocoding web services. geopy makes it easy for Python developers to locate the coordinates of addresses, cities, countries, and landmarks across the globe using third-party geocoders and other data sources. geopy includes geocoder classes for the `OpenStreetMap Nominatim`_, `Google Geocoding API (V3)`_, and many other geocoding services. The full list is available on the `Geocoders doc section`_. Geocoder classes are located in `geopy.geocoders`_. .. _OpenStreetMap Nominatim: https://nominatim.org .. _Google Geocoding API (V3): https://developers.google.com/maps/documentation/geocoding/ .. _Geocoders doc section: https://geopy.readthedocs.io/en/latest/#geocoders .. _geopy.geocoders: https://github.com/geopy/geopy/tree/master/geopy/geocoders geopy is tested against CPython (versions 3.5, 3.6, 3.7, 3.8, 3.9) and PyPy3. geopy 1.x line also supported CPython 2.7, 3.4 and PyPy2. © geopy contributors 2006-2018 (see AUTHORS) under the `MIT License `__. Installation ------------ Install using `pip `__ with: :: pip install geopy Or, `download a wheel or source archive from PyPI `__. Geocoding --------- To geolocate a query to an address and coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.geocode("175 5th Avenue NYC") >>> print(location.address) Flatiron Building, 175, 5th Avenue, Flatiron, New York, NYC, New York, ... >>> print((location.latitude, location.longitude)) (40.7410861, -73.9896297241625) >>> print(location.raw) {'place_id': '9167009604', 'type': 'attraction', ...} To find the address corresponding to a set of coordinates: .. code:: pycon >>> from geopy.geocoders import Nominatim >>> geolocator = Nominatim(user_agent="specify_your_app_name_here") >>> location = geolocator.reverse("52.509669, 13.376294") >>> print(location.address) Potsdamer Platz, Mitte, Berlin, 10117, Deutschland, European Union >>> print((location.latitude, location.longitude)) (52.5094982, 13.3765983) >>> print(location.raw) {'place_id': '654513', 'osm_type': 'node', ...} Measuring Distance ------------------ Geopy can calculate geodesic distance between two points using the `geodesic distance `_ or the `great-circle distance `_, with a default of the geodesic distance available as the function `geopy.distance.distance`. Here's an example usage of the geodesic distance, taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import geodesic >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(geodesic(newport_ri, cleveland_oh).miles) 538.390445368 Using great-circle distance, also taking pair of :code:`(lat, lon)` tuples: .. code:: pycon >>> from geopy.distance import great_circle >>> newport_ri = (41.49008, -71.312796) >>> cleveland_oh = (41.499498, -81.695391) >>> print(great_circle(newport_ri, cleveland_oh).miles) 536.997990696 Documentation ------------- More documentation and examples can be found at `Read the Docs `__. Keywords: geocode geocoding gis geographical maps earth distance Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Scientific/Engineering :: GIS Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.5 Provides-Extra: dev-lint Provides-Extra: timezone Provides-Extra: dev-docs Provides-Extra: aiohttp Provides-Extra: dev-test Provides-Extra: requests Provides-Extra: dev geopy-2.2.0/geopy.egg-info/SOURCES.txt0000644000076500000240000000460114072560236020002 0ustar kostyastaff00000000000000AUTHORS LICENSE MANIFEST.in README.rst pytest.ini setup.cfg setup.py geopy/__init__.py geopy/adapters.py geopy/compat.py geopy/distance.py geopy/exc.py geopy/format.py geopy/location.py geopy/point.py geopy/timezone.py geopy/units.py geopy/util.py geopy.egg-info/PKG-INFO geopy.egg-info/SOURCES.txt geopy.egg-info/dependency_links.txt geopy.egg-info/requires.txt geopy.egg-info/top_level.txt geopy/extra/__init__.py geopy/extra/rate_limiter.py geopy/geocoders/__init__.py geopy/geocoders/algolia.py geopy/geocoders/arcgis.py geopy/geocoders/azure.py geopy/geocoders/baidu.py geopy/geocoders/banfrance.py geopy/geocoders/base.py geopy/geocoders/bing.py geopy/geocoders/databc.py geopy/geocoders/geocodeearth.py geopy/geocoders/geocodio.py geopy/geocoders/geolake.py geopy/geocoders/geonames.py geopy/geocoders/google.py geopy/geocoders/googlev3.py geopy/geocoders/here.py geopy/geocoders/ignfrance.py geopy/geocoders/mapbox.py geopy/geocoders/mapquest.py geopy/geocoders/maptiler.py geopy/geocoders/nominatim.py geopy/geocoders/opencage.py geopy/geocoders/openmapquest.py geopy/geocoders/osm.py geopy/geocoders/pelias.py geopy/geocoders/photon.py geopy/geocoders/pickpoint.py geopy/geocoders/smartystreets.py geopy/geocoders/tomtom.py geopy/geocoders/what3words.py geopy/geocoders/yandex.py test/__init__.py test/conftest.py test/proxy_server.py test/selfsigned_ca.pem test/test_distance.py test/test_exc.py test/test_format.py test/test_init.py test/test_location.py test/test_point.py test/test_timezone.py test/adapters/__init__.py test/adapters/each_adapter.py test/adapters/retry_after.py test/extra/__init__.py test/extra/rate_limiter.py test/geocoders/__init__.py test/geocoders/algolia.py test/geocoders/arcgis.py test/geocoders/azure.py test/geocoders/baidu.py test/geocoders/banfrance.py test/geocoders/base.py test/geocoders/bing.py test/geocoders/databc.py test/geocoders/geocodeearth.py test/geocoders/geocodio.py test/geocoders/geolake.py test/geocoders/geonames.py test/geocoders/googlev3.py test/geocoders/here.py test/geocoders/ignfrance.py test/geocoders/mapbox.py test/geocoders/mapquest.py test/geocoders/maptiler.py test/geocoders/nominatim.py test/geocoders/opencage.py test/geocoders/openmapquest.py test/geocoders/pelias.py test/geocoders/photon.py test/geocoders/pickpoint.py test/geocoders/smartystreets.py test/geocoders/tomtom.py test/geocoders/util.py test/geocoders/what3words.py test/geocoders/yandex.pygeopy-2.2.0/geopy.egg-info/dependency_links.txt0000644000076500000240000000000114072560236022163 0ustar kostyastaff00000000000000 geopy-2.2.0/geopy.egg-info/requires.txt0000644000076500000240000000074014072560236020516 0ustar kostyastaff00000000000000geographiclib<2,>=1.49 [aiohttp] aiohttp [dev] async_generator coverage flake8<3.9.0,>=3.8.0 isort<5.7.0,>=5.6.0 pytest-aiohttp pytest>=3.10 readme_renderer sphinx sphinx-issues sphinx_rtd_theme>=0.5.0 [dev-docs] readme_renderer sphinx sphinx-issues sphinx_rtd_theme>=0.5.0 [dev-lint] async_generator flake8<3.9.0,>=3.8.0 isort<5.7.0,>=5.6.0 [dev-test] async_generator coverage pytest-aiohttp pytest>=3.10 sphinx [requests] urllib3>=1.24.2 requests>=2.16.2 [timezone] pytz geopy-2.2.0/geopy.egg-info/top_level.txt0000644000076500000240000000000614072560236020643 0ustar kostyastaff00000000000000geopy geopy-2.2.0/pytest.ini0000644000076500000240000000047214032167233015330 0ustar kostyastaff00000000000000[pytest] python_files = test/test_*.py test/adapters/*.py test/extra/*.py test/geocoders/*.py ; Bodies of HTTP errors are logged with INFO level log_level = INFO ; Show warnings. Similar to `python -Wd`. filterwarnings = d ; Show skip reasons ; Print shorter tracebacks addopts = -ra --tb=short geopy-2.2.0/setup.cfg0000644000076500000240000000035614072560236015125 0ustar kostyastaff00000000000000[flake8] max-complexity = 24 max-line-length = 90 [isort] combine_as_imports = True force_grid_wrap = 0 include_trailing_comma = True known_first_party = test line_length = 88 multi_line_output = 3 [egg_info] tag_build = tag_date = 0 geopy-2.2.0/setup.py0000755000076500000240000000641214032167233015014 0ustar kostyastaff00000000000000#!/usr/bin/env python """ geopy """ import sys from setuptools import find_packages, setup if sys.version_info < (3, 5): raise RuntimeError( "geopy 2 supports Python 3.5 and above. " "Use geopy 1.x if you need Python 2.7 or 3.4 support." ) # This import must be below the above `sys.version_info` check, # because the code being imported here is not compatible with the older # versions of Python. from geopy import __version__ as version # noqa # isort:skip INSTALL_REQUIRES = [ 'geographiclib<2,>=1.49', ] EXTRAS_DEV_TESTFILES_COMMON = [ "async_generator", ] EXTRAS_DEV_LINT = [ "flake8>=3.8.0,<3.9.0", "isort>=5.6.0,<5.7.0", ] EXTRAS_DEV_TEST = [ "coverage", "pytest-aiohttp", # for `async def` tests "pytest>=3.10", "sphinx", # `docutils` from sphinx is used in tests ] EXTRAS_DEV_DOCS = [ "readme_renderer", "sphinx", "sphinx-issues", "sphinx_rtd_theme>=0.5.0", ] setup( name='geopy', version=version, description='Python Geocoding Toolbox', long_description=open('README.rst').read(), maintainer='Kostya Esmukov', maintainer_email='kostya@esmukov.ru', url='https://github.com/geopy/geopy', download_url=( 'https://github.com/geopy/geopy/archive/%s.tar.gz' % version ), packages=find_packages(exclude=["*test*"]), install_requires=INSTALL_REQUIRES, extras_require={ "dev": sorted(set( EXTRAS_DEV_TESTFILES_COMMON + EXTRAS_DEV_LINT + EXTRAS_DEV_TEST + EXTRAS_DEV_DOCS )), "dev-lint": (EXTRAS_DEV_TESTFILES_COMMON + EXTRAS_DEV_LINT), "dev-test": (EXTRAS_DEV_TESTFILES_COMMON + EXTRAS_DEV_TEST), "dev-docs": EXTRAS_DEV_DOCS, "aiohttp": ["aiohttp"], "requests": [ "urllib3>=1.24.2", # ^^^ earlier versions would work, but a custom ssl # context would silently have system certificates be loaded as # trusted: https://github.com/urllib3/urllib3/pull/1566 "requests>=2.16.2", # ^^^ earlier versions would work, but they use an older # vendored version of urllib3 (see note above) ], "timezone": ["pytz"], }, license='MIT', keywords='geocode geocoding gis geographical maps earth distance', python_requires=">=3.5", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Scientific/Engineering :: GIS", "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] ) geopy-2.2.0/test/0000755000076500000240000000000014072560236014257 5ustar kostyastaff00000000000000geopy-2.2.0/test/__init__.py0000644000076500000240000000000013374037226016360 0ustar kostyastaff00000000000000geopy-2.2.0/test/adapters/0000755000076500000240000000000014072560236016062 5ustar kostyastaff00000000000000geopy-2.2.0/test/adapters/__init__.py0000644000076500000240000000000014032167233020155 0ustar kostyastaff00000000000000geopy-2.2.0/test/adapters/each_adapter.py0000644000076500000240000003240014032167233021027 0ustar kostyastaff00000000000000import os import ssl from unittest.mock import patch from urllib.parse import urljoin from urllib.request import getproxies, urlopen import pytest from async_generator import async_generator, asynccontextmanager, yield_ import geopy.geocoders from geopy.adapters import ( AdapterHTTPError, AioHTTPAdapter, BaseAsyncAdapter, RequestsAdapter, URLLibAdapter, ) from geopy.exc import ( GeocoderAuthenticationFailure, GeocoderParseError, GeocoderServiceError, ) from geopy.geocoders.base import Geocoder from test.proxy_server import HttpServerThread, ProxyServerThread CERT_SELFSIGNED_CA = os.path.join(os.path.dirname(__file__), "..", "selfsigned_ca.pem") # Are system proxies set? System proxies are set in: # - Environment variables (HTTP_PROXY/HTTPS_PROXY) on Unix; # - System Configuration Framework on macOS; # - Registry's Internet Settings section on Windows. WITH_SYSTEM_PROXIES = bool(getproxies()) AVAILABLE_ADAPTERS = [URLLibAdapter] NOT_AVAILABLE_ADAPTERS = [] try: import requests # noqa except ImportError: NOT_AVAILABLE_ADAPTERS.append(RequestsAdapter) else: AVAILABLE_ADAPTERS.append(RequestsAdapter) try: import aiohttp # noqa except ImportError: NOT_AVAILABLE_ADAPTERS.append(AioHTTPAdapter) else: AVAILABLE_ADAPTERS.append(AioHTTPAdapter) class DummyGeocoder(Geocoder): def geocode(self, location, *, is_json=False): return self._call_geocoder(location, lambda res: res, is_json=is_json) @pytest.fixture(scope="session") def timeout(): return 5 @pytest.fixture(scope="session") def proxy_server_thread(timeout): with ProxyServerThread(timeout=timeout) as proxy_server: yield proxy_server @pytest.fixture def proxy_server(proxy_server_thread): proxy_server_thread.reset() return proxy_server_thread @pytest.fixture(params=[True, False]) def proxy_url(request, proxy_server): # Parametrize with proxy urls with and without scheme, e.g. # - `http://localhost:8080` # - `localhost:8080` with_scheme = request.param return proxy_server.get_proxy_url(with_scheme) @pytest.mark.skipif(WITH_SYSTEM_PROXIES, reason="There're active system proxies") @pytest.fixture def inject_proxy_to_system_env(proxy_url): assert os.environ.get("http_proxy") is None assert os.environ.get("https_proxy") is None os.environ["http_proxy"] = proxy_url os.environ["https_proxy"] = proxy_url yield os.environ.pop("http_proxy", None) os.environ.pop("https_proxy", None) @pytest.fixture(scope="session") def http_server(timeout): with HttpServerThread(timeout=timeout) as http_server: yield http_server @pytest.fixture def remote_website_trusted_https(skip_if_internet_access_is_not_allowed): return "https://httpbin.org/html" # must be trusted by the system CAs @pytest.fixture def remote_website_http(http_server): return http_server.get_server_url() @pytest.fixture def remote_website_http_json(remote_website_http): return urljoin(remote_website_http, "/json") @pytest.fixture def remote_website_http_json_plain(remote_website_http): return urljoin(remote_website_http, "/json/plain") @pytest.fixture def remote_website_http_404(remote_website_http): return urljoin(remote_website_http, "/404") @pytest.fixture(params=AVAILABLE_ADAPTERS, autouse=True) def adapter_factory(request): adapter_factory = request.param with patch.object( geopy.geocoders.options, "default_adapter_factory", adapter_factory ): yield adapter_factory @asynccontextmanager @async_generator async def make_dummy_async_geocoder(**kwargs): geocoder = DummyGeocoder(**kwargs) run_async = isinstance(geocoder.adapter, BaseAsyncAdapter) if run_async: async with geocoder: await yield_(geocoder) else: orig_geocode = geocoder.geocode async def geocode(*args, **kwargs): return orig_geocode(*args, **kwargs) geocoder.geocode = geocode await yield_(geocoder) @pytest.mark.parametrize("adapter_cls", NOT_AVAILABLE_ADAPTERS) async def test_not_available_adapters_raise(adapter_cls): # Note: this test is uselessly parametrized with `adapter_factory`. with patch.object( geopy.geocoders.options, "default_adapter_factory", adapter_cls ): with pytest.raises(ImportError): async with make_dummy_async_geocoder(): pass async def test_geocoder_constructor_uses_https_proxy( timeout, proxy_server, proxy_url, remote_website_trusted_https ): base_http = urlopen(remote_website_trusted_https, timeout=timeout) base_html = base_http.read().decode() async with make_dummy_async_geocoder( proxies={"https": proxy_url}, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) assert base_html == await geocoder_dummy.geocode(remote_website_trusted_https) assert 1 == len(proxy_server.requests) @pytest.mark.parametrize("with_scheme", [False, True]) async def test_geocoder_http_proxy_auth_is_respected( timeout, proxy_server, remote_website_http, with_scheme ): proxy_server.set_auth("user", "test") base_http = urlopen(remote_website_http, timeout=timeout) base_html = base_http.read().decode() proxy_url = proxy_server.get_proxy_url(with_scheme=with_scheme) async with make_dummy_async_geocoder( proxies={"http": proxy_url}, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) assert base_html == await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) @pytest.mark.parametrize("with_scheme", [False, True]) async def test_geocoder_https_proxy_auth_is_respected( timeout, proxy_server, remote_website_trusted_https, with_scheme ): proxy_server.set_auth("user", "test") base_http = urlopen(remote_website_trusted_https, timeout=timeout) base_html = base_http.read().decode() proxy_url = proxy_server.get_proxy_url(with_scheme=with_scheme) async with make_dummy_async_geocoder( proxies={"https": proxy_url}, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) assert base_html == await geocoder_dummy.geocode(remote_website_trusted_https) assert 1 == len(proxy_server.requests) async def test_geocoder_http_proxy_auth_error( timeout, proxy_server, proxy_url, remote_website_http ): # Set up proxy auth but query it without auth: proxy_server.set_auth("user", "test") async with make_dummy_async_geocoder( proxies={"http": proxy_url}, timeout=timeout ) as geocoder_dummy: # For HTTP targets we cannot distinguish between 401 from proxy # and 401 from geocoding service, thus the same error as for # 401 for geocoders is raised: GeocoderAuthenticationFailure with pytest.raises(GeocoderAuthenticationFailure) as exc_info: await geocoder_dummy.geocode(remote_website_http) assert exc_info.type is GeocoderAuthenticationFailure async def test_geocoder_https_proxy_auth_error( timeout, proxy_server, proxy_url, remote_website_trusted_https ): # Set up proxy auth but query it without auth: proxy_server.set_auth("user", "test") async with make_dummy_async_geocoder( proxies={"https": proxy_url}, timeout=timeout ) as geocoder_dummy: with pytest.raises(GeocoderServiceError) as exc_info: await geocoder_dummy.geocode(remote_website_trusted_https) assert exc_info.type is GeocoderServiceError async def test_ssl_context_with_proxy_is_respected( timeout, proxy_server, proxy_url, remote_website_trusted_https ): # Create an ssl context which should not allow the negotiation with # the `self.remote_website_https`. bad_ctx = ssl.create_default_context(cafile=CERT_SELFSIGNED_CA) async with make_dummy_async_geocoder( proxies={"https": proxy_url}, ssl_context=bad_ctx, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) with pytest.raises(GeocoderServiceError) as excinfo: await geocoder_dummy.geocode(remote_website_trusted_https) assert "SSL" in str(excinfo.value) assert 1 <= len(proxy_server.requests) @pytest.mark.skipif(WITH_SYSTEM_PROXIES, reason="There're active system proxies") async def test_ssl_context_without_proxy_is_respected( timeout, remote_website_trusted_https ): # Create an ssl context which should not allow the negotiation with # the `self.remote_website_https`. bad_ctx = ssl.create_default_context(cafile=CERT_SELFSIGNED_CA) async with make_dummy_async_geocoder( ssl_context=bad_ctx, timeout=timeout ) as geocoder_dummy: with pytest.raises(GeocoderServiceError) as excinfo: await geocoder_dummy.geocode(remote_website_trusted_https) assert "SSL" in str(excinfo.value) async def test_geocoder_constructor_uses_http_proxy( timeout, proxy_server, proxy_url, remote_website_http ): base_http = urlopen(remote_website_http, timeout=timeout) base_html = base_http.read().decode() async with make_dummy_async_geocoder( proxies={"http": proxy_url}, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) assert base_html == await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) async def test_geocoder_constructor_uses_str_proxy( timeout, proxy_server, proxy_url, remote_website_http ): base_http = urlopen(remote_website_http, timeout=timeout) base_html = base_http.read().decode() async with make_dummy_async_geocoder( proxies=proxy_url, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) assert base_html == await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) async def test_geocoder_constructor_has_both_schemes_proxy(proxy_url): g = DummyGeocoder(proxies=proxy_url, scheme="http") assert g.proxies == {"http": proxy_url, "https": proxy_url} async def test_get_json(remote_website_http_json, timeout): async with make_dummy_async_geocoder(timeout=timeout) as geocoder_dummy: result = await geocoder_dummy.geocode(remote_website_http_json, is_json=True) assert isinstance(result, dict) async def test_get_json_plain(remote_website_http_json_plain, timeout): async with make_dummy_async_geocoder(timeout=timeout) as geocoder_dummy: result = await geocoder_dummy.geocode( remote_website_http_json_plain, is_json=True ) assert isinstance(result, dict) async def test_get_json_failure_on_non_json(remote_website_http, timeout): async with make_dummy_async_geocoder(timeout=timeout) as geocoder_dummy: with pytest.raises(GeocoderParseError): await geocoder_dummy.geocode(remote_website_http, is_json=True) async def test_adapter_exception_for_non_200_response(remote_website_http_404, timeout): async with make_dummy_async_geocoder(timeout=timeout) as geocoder_dummy: with pytest.raises(GeocoderServiceError) as excinfo: await geocoder_dummy.geocode(remote_website_http_404) assert isinstance(excinfo.value, GeocoderServiceError) assert isinstance(excinfo.value.__cause__, AdapterHTTPError) assert isinstance(excinfo.value.__cause__, IOError) adapter_http_error = excinfo.value.__cause__ assert adapter_http_error.status_code == 404 assert adapter_http_error.headers['x-test-header'] == 'hello' assert adapter_http_error.text == 'Not found' async def test_system_proxies_are_respected_by_default( inject_proxy_to_system_env, timeout, proxy_server, remote_website_http, adapter_factory, proxy_url, ): async with make_dummy_async_geocoder(timeout=timeout) as geocoder_dummy: assert 0 == len(proxy_server.requests) await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) async def test_system_proxies_are_respected_with_none( inject_proxy_to_system_env, timeout, proxy_server, remote_website_http, adapter_factory, proxy_url, ): # proxies=None means "use system proxies", e.g. from the ENV. async with make_dummy_async_geocoder( proxies=None, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) async def test_system_proxies_are_reset_with_empty_dict( inject_proxy_to_system_env, timeout, proxy_server, remote_website_http ): async with make_dummy_async_geocoder(proxies={}, timeout=timeout) as geocoder_dummy: assert 0 == len(proxy_server.requests) await geocoder_dummy.geocode(remote_website_http) assert 0 == len(proxy_server.requests) async def test_string_value_overrides_system_proxies( inject_proxy_to_system_env, timeout, proxy_server, proxy_url, remote_website_http ): os.environ["http_proxy"] = "localhost:1" os.environ["https_proxy"] = "localhost:1" async with make_dummy_async_geocoder( proxies=proxy_url, timeout=timeout ) as geocoder_dummy: assert 0 == len(proxy_server.requests) await geocoder_dummy.geocode(remote_website_http) assert 1 == len(proxy_server.requests) geopy-2.2.0/test/adapters/retry_after.py0000644000076500000240000000132514032167233020757 0ustar kostyastaff00000000000000import datetime import time from unittest.mock import patch import pytest from geopy.adapters import get_retry_after @pytest.mark.parametrize( "headers, expected_retry_after", [ ({}, None), ({"retry-after": "42"}, 42), ({"retry-after": "Wed, 21 Oct 2015 07:28:44 GMT"}, 43), ({"retry-after": "Wed, 21 Oct 2015 06:28:44 GMT"}, 0), ({"retry-after": "Wed"}, None), ], ) def test_get_retry_after(headers, expected_retry_after): current_time = datetime.datetime( 2015, 10, 21, 7, 28, 1, tzinfo=datetime.timezone.utc ).timestamp() with patch.object(time, "time", return_value=current_time): assert expected_retry_after == get_retry_after(headers) geopy-2.2.0/test/conftest.py0000644000076500000240000003046214032167233016457 0ustar kostyastaff00000000000000import asyncio import atexit import contextlib import importlib import inspect import os import types from collections import defaultdict from functools import partial from statistics import mean, median from time import sleep from timeit import default_timer from unittest.mock import patch from urllib.parse import urlparse import pytest import geopy.geocoders from geopy.adapters import AdapterHTTPError, BaseAsyncAdapter, BaseSyncAdapter from geopy.geocoders.base import _DEFAULT_ADAPTER_CLASS # pytest-aiohttp calls `inspect.isasyncgenfunction` to detect # async generators in fixtures. # To support Python 3.5 we use `async_generator` library. # However: # - Since Python 3.6 there is a native implementation of # `inspect.isasyncgenfunction`, but it returns False # for `async_generator`'s functions. # - The stock `async_generator.isasyncgenfunction` doesn't detect # generators wrapped in `@pytest.fixture`. # # Thus we resort to monkey-patching it (for now). if getattr(inspect, "isasyncgenfunction", None) is not None: # >=py36 original_isasyncgenfunction = inspect.isasyncgenfunction else: # ==py35 original_isasyncgenfunction = lambda func: False # noqa def isasyncgenfunction(obj): if original_isasyncgenfunction(obj): return True # Detect async_generator function, possibly wrapped in `@pytest.fixture`: # See https://github.com/python-trio/async_generator/blob/v1.10/async_generator/_impl.py#L451-L455 # noqa return bool(getattr(obj, "_async_gen_function", None)) inspect.isasyncgenfunction = isasyncgenfunction def load_adapter_cls(adapter_ref): actual_adapter_class = _DEFAULT_ADAPTER_CLASS if adapter_ref: module_s, cls_s = adapter_ref.rsplit(".", 1) module = importlib.import_module(module_s) actual_adapter_class = getattr(module, cls_s) return actual_adapter_class max_retries = int(os.getenv('GEOPY_TEST_RETRIES', 2)) error_wait_seconds = float(os.getenv('GEOPY_TEST_ERROR_WAIT_SECONDS', 3)) no_retries_for_hosts = set(os.getenv('GEOPY_TEST_NO_RETRIES_FOR_HOSTS', '').split(',')) default_adapter = load_adapter_cls(os.getenv('GEOPY_TEST_ADAPTER')) default_adapter_is_async = issubclass(default_adapter, BaseAsyncAdapter) retry_status_codes = ( 403, # Forbidden (probably due to a rate limit) 429, # Too Many Requests (definitely a rate limit) 502, # Bad Gateway ) def pytest_report_header(config): internet_access = "allowed" if _is_internet_access_allowed(config) else "disabled" adapter_type = "async" if default_adapter_is_async else "sync" return ( "geopy:\n" " internet access: %s\n" " adapter: %r\n" " adapter type: %s" % (internet_access, default_adapter, adapter_type) ) def pytest_addoption(parser): # This option will probably be used in downstream packages, # thus it should be considered a public interface. parser.addoption( "--skip-tests-requiring-internet", action="store_true", help="Skip tests requiring Internet access.", ) def _is_internet_access_allowed(config): return not config.getoption("--skip-tests-requiring-internet") @pytest.fixture(scope='session') def is_internet_access_allowed(request): return _is_internet_access_allowed(request.config) @pytest.fixture def skip_if_internet_access_is_not_allowed(is_internet_access_allowed): # Used in test_adapters.py, which doesn't use the injected adapter below. if not is_internet_access_allowed: pytest.skip("Skipping a test requiring Internet access") @pytest.fixture(autouse=True, scope="session") def loop(): # Geocoder instances have class scope, so the event loop # should have session scope. loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) return loop def netloc_from_url(url): return urlparse(url).netloc def pretty_dict_format(heading, dict_to_format, item_prefix=' ', legend='', value_mapper=lambda v: v): s = [heading] if not dict_to_format: s.append(item_prefix + '-- empty --') else: max_key_len = max(len(k) for k in dict_to_format.keys()) for k, v in sorted(dict_to_format.items()): s.append('%s%s%s' % (item_prefix, k.ljust(max_key_len + 2), value_mapper(v))) if legend: s.append('') s.append('* %s' % legend) s.append('') # trailing newline return '\n'.join(s) class RequestsMonitor: """RequestsMonitor holds statistics of Adapter requests.""" def __init__(self): self.host_stats = defaultdict(lambda: dict(count=0, retries=0, times=[])) def record_request(self, url): hostname = netloc_from_url(url) self.host_stats[hostname]['count'] += 1 def record_retry(self, url): hostname = netloc_from_url(url) self.host_stats[hostname]['retries'] += 1 @contextlib.contextmanager def record_response(self, url): start = default_timer() try: yield finally: end = default_timer() hostname = netloc_from_url(url) self.host_stats[hostname]['times'].append(end - start) def __str__(self): def value_mapper(v): tv = v['times'] times_format = ( "min:%5.2fs, median:%5.2fs, max:%5.2fs, mean:%5.2fs, total:%5.2fs" ) if tv: # min/max require a non-empty sequence. times = times_format % (min(tv), median(tv), max(tv), mean(tv), sum(tv)) else: nan = float("nan") times = times_format % (nan, nan, nan, nan, 0) count = "count:%3d" % v['count'] retries = "retries:%3d" % v['retries'] if v['retries'] else "" return "; ".join(s for s in (count, times, retries) if s) legend = ( "count – number of requests (excluding retries); " "min, median, max, mean, total – request duration statistics " "(excluding failed requests); retries – number of retries." ) return pretty_dict_format('Request statistics per hostname', self.host_stats, legend=legend, value_mapper=value_mapper) @pytest.fixture(scope='session') def requests_monitor(): return RequestsMonitor() @pytest.fixture(autouse=True, scope='session') def print_requests_monitor_report(requests_monitor): yield def report(): print(str(requests_monitor)) # https://github.com/pytest-dev/pytest/issues/2704 # https://stackoverflow.com/a/38806934 atexit.register(report) @pytest.fixture(scope='session') def retries_enabled_session(): return types.SimpleNamespace(value=True) @pytest.fixture def disable_adapter_retries(retries_enabled_session): retries_enabled_session.value = False yield retries_enabled_session.value = True @pytest.fixture(autouse=True, scope='session') def patch_adapter( requests_monitor, retries_enabled_session, is_internet_access_allowed ): """ Patch the default Adapter to provide the following features: - Retry failed requests. Makes test runs more stable. - Track statistics with RequestsMonitor. - Skip tests requiring Internet access when Internet access is not allowed. """ if default_adapter_is_async: class AdapterProxy(BaseAdapterProxy, BaseAsyncAdapter): async def __aenter__(self): assert await self.adapter.__aenter__() is self.adapter return self async def __aexit__(self, exc_type, exc_val, exc_tb): return await self.adapter.__aexit__(exc_type, exc_val, exc_tb) async def _wrapped_get(self, url, do_request): res = None gen = self._retries(url) while True: try: next(gen) except StopIteration: break assert res is None try: res = await do_request() except Exception as e: error_wait_seconds = gen.throw(e) await asyncio.sleep(error_wait_seconds) else: assert gen.send(res) is None assert res is not None return res else: class AdapterProxy(BaseAdapterProxy, BaseSyncAdapter): def _wrapped_get(self, url, do_request): res = None gen = self._retries(url) while True: try: next(gen) except StopIteration: break assert res is None try: res = do_request() except Exception as e: error_wait_seconds = gen.throw(e) sleep(error_wait_seconds) else: assert gen.send(res) is None assert res is not None return res # In order to take advantage of Keep-Alives in tests, the actual Adapter # should be persisted between the test runs, so this fixture must be # in the "session" scope. adapter_factory = partial( AdapterProxy, adapter_factory=default_adapter, requests_monitor=requests_monitor, retries_enabled_session=retries_enabled_session, is_internet_access_allowed=is_internet_access_allowed, ) with patch.object( geopy.geocoders.options, "default_adapter_factory", adapter_factory ): yield class BaseAdapterProxy: def __init__( self, *, proxies, ssl_context, adapter_factory, requests_monitor, retries_enabled_session, is_internet_access_allowed ): self.adapter = adapter_factory( proxies=proxies, ssl_context=ssl_context, ) self.requests_monitor = requests_monitor self.retries_enabled_session = retries_enabled_session self.is_internet_access_allowed = is_internet_access_allowed def get_json(self, url, *, timeout, headers): return self._wrapped_get( url, partial(self.adapter.get_json, url, timeout=timeout, headers=headers), ) def get_text(self, url, *, timeout, headers): return self._wrapped_get( url, partial(self.adapter.get_text, url, timeout=timeout, headers=headers), ) def _retries(self, url): if not self.is_internet_access_allowed: # Assume that *all* geocoders require Internet access pytest.skip("Skipping a test requiring Internet access") self.requests_monitor.record_request(url) netloc = netloc_from_url(url) retries = max_retries if netloc in no_retries_for_hosts: retries = 0 for i in range(retries + 1): try: with self.requests_monitor.record_response(url): yield except AdapterHTTPError as error: if not self.retries_enabled_session.value: raise if i == retries or error.status_code not in retry_status_codes: # Note: we shouldn't blindly retry on any >=400 code, # because some of them are actually expected in tests # (like input validation verification). # TODO Retry failures with the 200 code? # Some geocoders return failures with 200 code # (like GoogleV3 for Quota Exceeded). # Should we detect this somehow to restart such requests? # # Re-raise -- don't retry this request raise else: # Swallow the error and retry the request pass except Exception: if i == retries: raise else: yield None return self.requests_monitor.record_retry(url) yield error_wait_seconds raise RuntimeError("Should not have been reached") geopy-2.2.0/test/extra/0000755000076500000240000000000014072560236015402 5ustar kostyastaff00000000000000geopy-2.2.0/test/extra/__init__.py0000644000076500000240000000000013673435726017514 0ustar kostyastaff00000000000000geopy-2.2.0/test/extra/rate_limiter.py0000644000076500000240000001202513700135050020421 0ustar kostyastaff00000000000000import asyncio from unittest.mock import MagicMock, patch, sentinel import pytest from geopy.exc import GeocoderQuotaExceeded, GeocoderServiceError from geopy.extra.rate_limiter import AsyncRateLimiter, RateLimiter @pytest.fixture(params=[False, True]) def is_async(request): return request.param @pytest.fixture def auto_async(is_async): if is_async: async def auto_async(coro): return await coro else: async def auto_async(result): return result return auto_async @pytest.fixture def auto_async_side_effect(is_async): def auto_async_side_effect(side_effect): if not is_async: return side_effect mock = MagicMock(side_effect=side_effect) async def func(*args, **kwargs): return mock(*args, **kwargs) return func return auto_async_side_effect @pytest.fixture def rate_limiter_cls(is_async): if is_async: return AsyncRateLimiter else: return RateLimiter @pytest.fixture def mock_clock(rate_limiter_cls): with patch.object(rate_limiter_cls, '_clock') as mock_clock: yield mock_clock @pytest.fixture def mock_sleep(auto_async_side_effect, rate_limiter_cls): with patch.object(rate_limiter_cls, '_sleep') as mock_sleep: mock_sleep.side_effect = auto_async_side_effect(None) yield mock_sleep async def test_min_delay( rate_limiter_cls, mock_clock, mock_sleep, auto_async_side_effect, auto_async ): mock_func = MagicMock() mock_func.side_effect = auto_async_side_effect(None) min_delay = 3.5 mock_clock.side_effect = [1] rl = rate_limiter_cls(mock_func, min_delay_seconds=min_delay) # First call -- no delay clock_first = 10 mock_clock.side_effect = [clock_first, clock_first] # no delay here await auto_async(rl(sentinel.arg, kwa=sentinel.kwa)) mock_sleep.assert_not_called() mock_func.assert_called_once_with(sentinel.arg, kwa=sentinel.kwa) # Second call after min_delay/3 seconds -- should be delayed clock_second = clock_first + (min_delay / 3) mock_clock.side_effect = [clock_second, clock_first + min_delay] await auto_async(rl(sentinel.arg, kwa=sentinel.kwa)) mock_sleep.assert_called_with(min_delay - (clock_second - clock_first)) mock_sleep.reset_mock() # Third call after min_delay*2 seconds -- no delay again clock_third = clock_first + min_delay + min_delay * 2 mock_clock.side_effect = [clock_third, clock_third] await auto_async(rl(sentinel.arg, kwa=sentinel.kwa)) mock_sleep.assert_not_called() async def test_max_retries( rate_limiter_cls, mock_clock, mock_sleep, auto_async_side_effect, auto_async ): mock_func = MagicMock() mock_clock.return_value = 1 rl = rate_limiter_cls( mock_func, max_retries=3, return_value_on_exception=sentinel.return_value, ) # Non-geopy errors must not be swallowed mock_func.side_effect = auto_async_side_effect(ValueError) with pytest.raises(ValueError): await auto_async(rl(sentinel.arg)) assert 1 == mock_func.call_count mock_func.reset_mock() # geopy errors must be swallowed and retried mock_func.side_effect = auto_async_side_effect(GeocoderServiceError) assert sentinel.return_value == await auto_async(rl(sentinel.arg)) assert 4 == mock_func.call_count mock_func.reset_mock() # Successful value must be returned mock_func.side_effect = auto_async_side_effect(side_effect=[ GeocoderServiceError, GeocoderServiceError, sentinel.good ]) assert sentinel.good == await auto_async(rl(sentinel.arg)) assert 3 == mock_func.call_count mock_func.reset_mock() # When swallowing is disabled, the exception must be raised rl.swallow_exceptions = False mock_func.side_effect = auto_async_side_effect(GeocoderQuotaExceeded) with pytest.raises(GeocoderQuotaExceeded): await auto_async(rl(sentinel.arg)) assert 4 == mock_func.call_count mock_func.reset_mock() async def test_error_wait_seconds( rate_limiter_cls, mock_clock, mock_sleep, auto_async_side_effect, auto_async ): mock_func = MagicMock() error_wait = 3.3 mock_clock.return_value = 1 rl = rate_limiter_cls( mock_func, max_retries=3, error_wait_seconds=error_wait, return_value_on_exception=sentinel.return_value, ) mock_func.side_effect = auto_async_side_effect(GeocoderServiceError) assert sentinel.return_value == await auto_async(rl(sentinel.arg)) assert 4 == mock_func.call_count assert 3 == mock_sleep.call_count mock_sleep.assert_called_with(error_wait) mock_func.reset_mock() async def test_sync_raises_for_awaitable(): def g(): # non-async function returning an awaitable -- like `geocode`. async def coro(): pass # pragma: no cover # Make a task from the coroutine, to avoid # the `coroutine 'coro' was never awaited` warning: task = asyncio.ensure_future(coro()) return task rl = RateLimiter(g) with pytest.raises(ValueError): await rl() geopy-2.2.0/test/geocoders/0000755000076500000240000000000014072560236016231 5ustar kostyastaff00000000000000geopy-2.2.0/test/geocoders/__init__.py0000644000076500000240000002506614034375174020356 0ustar kostyastaff00000000000000import importlib import inspect import pkgutil import docutils.core import docutils.utils import pytest import geopy.geocoders from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder skip_modules = [ "geopy.geocoders.base", # doesn't contain actual geocoders "geopy.geocoders.googlev3", # deprecated "geopy.geocoders.osm", # deprecated ] geocoder_modules = sorted( [ importlib.import_module(name) for _, name, _ in pkgutil.iter_modules( geopy.geocoders.__path__, "geopy.geocoders." ) if name not in skip_modules ], key=lambda m: m.__name__, ) geocoder_classes = sorted( { v for v in ( getattr(module, name) for module in geocoder_modules for name in dir(module) ) if inspect.isclass(v) and issubclass(v, Geocoder) and v is not Geocoder }, key=lambda cls: cls.__name__, ) def assert_no_varargs(sig): assert not [ str(p) for p in sig.parameters.values() if p.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD) ], ( "Geocoders must not have any (*args) or (**kwargs). " "See CONTRIBUTING.md for explanation." ) def assert_rst(sig, doc, allowed_rtypes=(None,)): # Parse RST from the docstring and generate an XML tree: doctree = docutils.core.publish_doctree( doc, settings_overrides={ "report_level": docutils.utils.Reporter.SEVERE_LEVEL + 1, }, ).asdom() def get_all_text(node): if node.nodeType == node.TEXT_NODE: return node.data else: text_string = "" for child_node in node.childNodes: if child_node.nodeName == "system_message": # skip warnings/errors continue if child_node.nodeName == "literal": tmpl = "``%s``" else: tmpl = "%s" text_string += tmpl % (get_all_text(child_node),) return text_string documented_rtype = None documented_types = {} documented_params = [] for field in doctree.getElementsByTagName("field"): field_name = get_all_text(field.getElementsByTagName("field_name")[0]) if field_name == "rtype": assert documented_rtype is None, "There must be single :rtype: directive" field_body = get_all_text(field.getElementsByTagName("field_body")[0]) assert field_body, ":rtype: directive must have a value" documented_rtype = field_body.replace("\n", " ") if field_name.startswith("type"): parts = field_name.split(" ") # ['type', 'ssl_context'] param_name = parts[-1] assert param_name not in documented_types, "Duplicate `type` definition" field_body = get_all_text(field.getElementsByTagName("field_body")[0]) documented_types[param_name] = field_body if field_name.startswith("param"): parts = field_name.split(" ") # ['param', 'str', 'query'] param_name = parts[-1] documented_params.append(param_name) if len(parts) == 3: assert param_name not in documented_types, "Duplicate `type` definition" documented_types[param_name] = parts[1] method_params = list(sig.parameters.keys())[1:] # skip `self` assert method_params == documented_params, ( "Actual method params set or order doesn't match the documented " ":param ...: directives in the docstring." ) missing_types = set(documented_params) - documented_types.keys() assert not missing_types, "Not all params have types" assert set(documented_types.keys()) == set(documented_params), ( "There are extraneous :type: directives" ) assert documented_rtype in allowed_rtypes def test_all_geocoders_are_exported_from_package(): expected = {cls.__name__ for cls in geocoder_classes} actual = set(dir(geopy.geocoders)) not_exported = expected - actual assert not not_exported, ( "These geocoders must be exported (via imports) " "in geopy/geocoders/__init__.py" ) def test_all_geocoders_are_listed_in_all(): expected = {cls.__name__ for cls in geocoder_classes} actual = set(geopy.geocoders.__all__) not_exported = expected - actual assert not not_exported, ( "These geocoders must be listed in the `__all__` tuple " "in geopy/geocoders/__init__.py" ) def test_all_geocoders_are_listed_in_service_to_geocoder(): assert set(geocoder_classes) == set(geopy.geocoders.SERVICE_TO_GEOCODER.values()), ( "All geocoders must be listed in the `SERVICE_TO_GEOCODER` dict " "in geopy/geocoders/__init__.py" ) @pytest.mark.parametrize("geocoder_module", geocoder_modules, ids=lambda m: m.__name__) def test_geocoder_module_all(geocoder_module): current_all = geocoder_module.__all__ expected_all = tuple( cls.__name__ for cls in geocoder_classes if cls.__module__ == geocoder_module.__name__ ) assert expected_all == current_all @pytest.mark.parametrize("geocoder_cls", geocoder_classes) def test_init_method_signature(geocoder_cls): method = geocoder_cls.__init__ sig = inspect.signature(method) assert_no_varargs(sig) sig_timeout = sig.parameters["timeout"] assert sig_timeout.kind == inspect.Parameter.KEYWORD_ONLY assert sig_timeout.default is DEFAULT_SENTINEL sig_proxies = sig.parameters["proxies"] assert sig_proxies.kind == inspect.Parameter.KEYWORD_ONLY assert sig_proxies.default is DEFAULT_SENTINEL sig_user_agent = sig.parameters["user_agent"] assert sig_user_agent.kind == inspect.Parameter.KEYWORD_ONLY assert sig_user_agent.default is None sig_ssl_context = sig.parameters["ssl_context"] assert sig_ssl_context.kind == inspect.Parameter.KEYWORD_ONLY assert sig_ssl_context.default is DEFAULT_SENTINEL sig_adapter_factory = sig.parameters["adapter_factory"] assert sig_adapter_factory.kind == inspect.Parameter.KEYWORD_ONLY assert sig_adapter_factory.default is None assert_rst(sig, method.__doc__) @pytest.mark.parametrize("geocoder_cls", geocoder_classes) def test_geocode_method_signature(geocoder_cls): # Every geocoder should have at least a `geocode` method. method = geocoder_cls.geocode sig = inspect.signature(method) assert_no_varargs(sig) # The first arg (except self) must be called `query`: sig_query = list(sig.parameters.values())[1] assert sig_query.name == "query" assert sig_query.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD # The rest must be kwargs-only: sig_kwargs = list(sig.parameters.values())[2:] assert all(p.kind == inspect.Parameter.KEYWORD_ONLY for p in sig_kwargs), ( "All method args except `query` must be keyword-only " "(i.e. separated with an `*`)." ) # kwargs must contain `exactly_one`: sig_exactly_one = sig.parameters["exactly_one"] assert sig_exactly_one.default is True, "`exactly_one` must be True" # kwargs must contain `timeout`: sig_timeout = sig.parameters["timeout"] assert sig_timeout.default is DEFAULT_SENTINEL, "`timeout` must be DEFAULT_SENTINEL" assert_rst( sig, method.__doc__, allowed_rtypes=[ ":class:`geopy.location.Location` or a list of them, " "if ``exactly_one=False``.", # what3words "``None``, :class:`geopy.location.Location` or a list of them, " "if ``exactly_one=False``.", ], ) @pytest.mark.parametrize( "geocoder_cls", [cls for cls in geocoder_classes if getattr(cls, "reverse", None)], ) def test_reverse_method_signature(geocoder_cls): # `reverse` method is optional. method = geocoder_cls.reverse sig = inspect.signature(method) assert_no_varargs(sig) # First arg (except self) must be called `query`: sig_query = list(sig.parameters.values())[1] assert sig_query.name == "query" assert sig_query.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD # The rest must be kwargs-only: sig_kwargs = list(sig.parameters.values())[2:] assert all(p.kind == inspect.Parameter.KEYWORD_ONLY for p in sig_kwargs), ( "All method args except `query` must be keyword-only " "(i.e. separated with an `*`)." ) # kwargs must contain `exactly_one`: sig_exactly_one = sig.parameters["exactly_one"] assert sig_exactly_one.default is True, "`exactly_one` must be True" # kwargs must contain `timeout`: sig_timeout = sig.parameters["timeout"] assert sig_timeout.default is DEFAULT_SENTINEL, "`timeout` must be DEFAULT_SENTINEL" assert_rst( sig, method.__doc__, allowed_rtypes=[ ":class:`geopy.location.Location` or a list of them, " # what3words "if ``exactly_one=False``.", "``None``, :class:`geopy.location.Location` or a list of them, " "if ``exactly_one=False``.", ], ) @pytest.mark.parametrize( "geocoder_cls", [cls for cls in geocoder_classes if getattr(cls, "reverse_timezone", None)], ) def test_reverse_timezone_method_signature(geocoder_cls): method = geocoder_cls.reverse_timezone sig = inspect.signature(method) assert_no_varargs(sig) # First arg (except self) must be called `query`: sig_query = list(sig.parameters.values())[1] assert sig_query.name == "query" assert sig_query.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD # The rest must be kwargs-only: sig_kwargs = list(sig.parameters.values())[2:] assert all(p.kind == inspect.Parameter.KEYWORD_ONLY for p in sig_kwargs), ( "All method args except `query` must be keyword-only " "(i.e. separated with an `*`)." ) # kwargs must contain `timeout`: sig_timeout = sig.parameters["timeout"] assert sig_timeout.default is DEFAULT_SENTINEL, "`timeout` must be DEFAULT_SENTINEL" assert_rst( sig, method.__doc__, allowed_rtypes=[ ":class:`geopy.timezone.Timezone`.", "``None`` or :class:`geopy.timezone.Timezone`.", ], ) @pytest.mark.parametrize("geocoder_cls", geocoder_classes) def test_no_extra_public_methods(geocoder_cls): methods = { n for n in dir(geocoder_cls) if not n.startswith("_") and inspect.isfunction(getattr(geocoder_cls, n)) } allowed = { "geocode", "reverse", "reverse_timezone", } assert methods <= allowed, ( "Geopy geocoders are currently allowed to only have these methods: %s" % allowed ) geopy-2.2.0/test/geocoders/algolia.py0000644000076500000240000000526414032167233020216 0ustar kostyastaff00000000000000from geopy.geocoders import AlgoliaPlaces from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestAlgoliaPlaces(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return AlgoliaPlaces( app_id=env.get('ALGOLIA_PLACES_APP_ID'), api_key=env.get('ALGOLIA_PLACES_API_KEY'), timeout=3, **kwargs) async def test_user_agent_custom(self): geocoder = self.make_geocoder( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_geocode(self): location = await self.geocode_run( {'query': 'москва'}, {'latitude': 55.75587, 'longitude': 37.61768}, ) assert 'Москва' in location.address async def test_reverse(self): location = await self.reverse_run( {'query': '51, -0.13', 'language': 'en'}, {'latitude': 51, 'longitude': -0.13}, ) assert 'A272' in location.address async def test_explicit_type(self): location = await self.geocode_run( {'query': 'Madrid', 'type': 'city', 'language': 'en'}, {}, ) assert 'Madrid' in location.address async def test_limit(self): limit = 5 locations = await self.geocode_run( {'query': 'Madrid', 'type': 'city', 'language': 'en', 'exactly_one': False, 'limit': limit}, {}, ) assert len(locations) == limit async def test_countries(self): countries = ["ES"] location = await self.geocode_run( {'query': 'Madrid', 'language': 'en', 'countries': countries}, {}, ) assert "Madrid" in location.address async def test_countries_no_result(self): countries = ["UA", "RU"] await self.geocode_run( {'query': 'Madrid', 'language': 'en', 'countries': countries}, {}, expect_failure=True ) async def test_geocode_no_result(self): await self.geocode_run( {'query': 'sldkfhdskjfhsdkhgflaskjgf'}, {}, expect_failure=True, ) async def test_around(self): await self.geocode_run( {'query': 'maple street', 'language': 'en', 'around': Point(51.1, -0.1)}, {'latitude': 51.5299, 'longitude': -0.0628044, "delta": 1}, ) await self.geocode_run( {'query': 'maple street', 'language': 'en', 'around': Point(50.1, 10.1)}, {'latitude': 50.0517, 'longitude': 10.1966, "delta": 1}, ) geopy-2.2.0/test/geocoders/arcgis.py0000644000076500000240000000641114036600434020050 0ustar kostyastaff00000000000000import pytest from geopy import exc from geopy.geocoders import ArcGIS from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestUnitArcGIS: def test_user_agent_custom(self): geocoder = ArcGIS( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestArcGIS(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return ArcGIS(timeout=3, **kwargs) async def test_missing_password_error(self): with pytest.raises(exc.ConfigurationError): ArcGIS(username='a') async def test_scheme_config_error(self): with pytest.raises(exc.ConfigurationError): ArcGIS( username='a', password='b', referer='http://www.example.com', scheme='http' ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab"}, {"latitude": 39.916, "longitude": 116.390}, ) async def test_empty_response(self): await self.geocode_run( {"query": "dksahdksahdjksahdoufydshf"}, {}, expect_failure=True ) async def test_geocode_with_out_fields_string(self): result = await self.geocode_run( {"query": "Trafalgar Square, London", "out_fields": "Country"}, {} ) assert result.raw['attributes'] == {'Country': 'GBR'} async def test_geocode_with_out_fields_list(self): result = await self.geocode_run( {"query": "Trafalgar Square, London", "out_fields": ["City", "Type"]}, {} ) assert result.raw['attributes'] == { 'City': 'London', 'Type': 'Tourist Attraction' } async def test_reverse_point(self): location = await self.reverse_run( {"query": Point(40.753898, -73.985071)}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667}, ) assert 'New York' in location.address async def test_reverse_not_exactly_one(self): await self.reverse_run( {"query": Point(40.753898, -73.985071), "exactly_one": False}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667}, ) async def test_reverse_no_result(self): await self.reverse_run( # Antarctica {"query": (-84.172382, 45.9873073)}, {}, expect_failure=True ) class TestArcGISAuthenticated(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return ArcGIS( username=env['ARCGIS_USERNAME'], password=env['ARCGIS_PASSWORD'], referer=env['ARCGIS_REFERER'], timeout=3, **kwargs ) async def test_basic_address(self): await self.geocode_run( {"query": "Potsdamer Platz, Berlin, Deutschland"}, {"latitude": 52.5094982, "longitude": 13.3765983}, ) geopy-2.2.0/test/geocoders/azure.py0000644000076500000240000000050014032167233017720 0ustar kostyastaff00000000000000from geopy.geocoders import AzureMaps from test.geocoders.tomtom import BaseTestTomTom from test.geocoders.util import env class TestAzureMaps(BaseTestTomTom): @classmethod def make_geocoder(cls, **kwargs): return AzureMaps(env['AZURE_SUBSCRIPTION_KEY'], timeout=3, **kwargs) geopy-2.2.0/test/geocoders/baidu.py0000644000076500000240000000554714032167233017676 0ustar kostyastaff00000000000000import pytest from geopy.exc import GeocoderAuthenticationFailure from geopy.geocoders import Baidu, BaiduV3 from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestUnitBaidu(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return Baidu( api_key='DUMMYKEY1234', user_agent='my_user_agent/1.0', **kwargs ) async def test_user_agent_custom(self): assert self.geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class BaseTestBaidu(BaseTestGeocoder): async def test_basic_address(self): await self.geocode_run( {"query": ( "\u5317\u4eac\u5e02\u6d77\u6dc0\u533a" "\u4e2d\u5173\u6751\u5927\u885727\u53f7" )}, {"latitude": 39.983615544507, "longitude": 116.32295155093}, ) async def test_reverse_point(self): await self.reverse_run( {"query": Point(39.983615544507, 116.32295155093)}, {"latitude": 39.983615544507, "longitude": 116.32295155093}, ) await self.reverse_run( {"query": Point(39.983615544507, 116.32295155093), "exactly_one": False}, {"latitude": 39.983615544507, "longitude": 116.32295155093}, ) class TestBaidu(BaseTestBaidu): @classmethod def make_geocoder(cls, **kwargs): return Baidu( api_key=env['BAIDU_KEY'], timeout=3, **kwargs, ) async def test_invalid_ak(self): async with self.inject_geocoder(Baidu(api_key='DUMMYKEY1234')): with pytest.raises(GeocoderAuthenticationFailure) as exc_info: await self.geocode_run({"query": "baidu"}, None) assert str(exc_info.value) == 'Invalid AK' class TestBaiduSK(BaseTestBaidu): @classmethod def make_geocoder(cls, **kwargs): return Baidu( api_key=env['BAIDU_KEY_REQUIRES_SK'], security_key=env['BAIDU_SEC_KEY'], timeout=3, **kwargs, ) async def test_sn_with_peculiar_chars(self): await self.geocode_run( {"query": ( "\u5317\u4eac\u5e02\u6d77\u6dc0\u533a" "\u4e2d\u5173\u6751\u5927\u885727\u53f7" " ' & = , ? %" )}, {"latitude": 39.983615544507, "longitude": 116.32295155093}, ) class TestBaiduV3(TestBaidu): @classmethod def make_geocoder(cls, **kwargs): return BaiduV3( api_key=env['BAIDU_V3_KEY'], timeout=3, **kwargs, ) class TestBaiduV3SK(TestBaiduSK): @classmethod def make_geocoder(cls, **kwargs): return BaiduV3( api_key=env['BAIDU_V3_KEY_REQUIRES_SK'], security_key=env['BAIDU_V3_SEC_KEY'], timeout=3, **kwargs, ) geopy-2.2.0/test/geocoders/banfrance.py0000644000076500000240000000221614032167233020517 0ustar kostyastaff00000000000000from geopy.geocoders import BANFrance from test.geocoders.util import BaseTestGeocoder class TestBANFrance(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return BANFrance(timeout=10, **kwargs) async def test_user_agent_custom(self): geocoder = BANFrance( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_geocode_with_address(self): location = await self.geocode_run( {"query": "Camp des Landes, 41200 VILLEFRANCHE-SUR-CHER"}, {"latitude": 47.293048, "longitude": 1.718985}, ) assert "Camp des Landes" in location.address async def test_reverse(self): location = await self.reverse_run( {"query": "48.154587,3.221237"}, {"latitude": 48.154587, "longitude": 3.221237}, ) assert "Collemiers" in location.address async def test_geocode_limit(self): result = await self.geocode_run( {"query": "8 bd du port", "limit": 2, "exactly_one": False}, {} ) assert 2 >= len(result) geopy-2.2.0/test/geocoders/base.py0000644000076500000240000002471113700135050017507 0ustar kostyastaff00000000000000import unittest from contextlib import ExitStack from unittest.mock import patch, sentinel import pytest import geopy.geocoders import geopy.geocoders.base from geopy.adapters import BaseAsyncAdapter, BaseSyncAdapter from geopy.exc import GeocoderNotFound, GeocoderQueryError from geopy.geocoders import GoogleV3, get_geocoder_for_service from geopy.geocoders.base import Geocoder, _synchronized from geopy.point import Point class DummySyncAdapter(BaseSyncAdapter): def get_json(self, *args, **kwargs): # pragma: no cover raise NotImplementedError def get_text(self, *args, **kwargs): # pragma: no cover raise NotImplementedError class DummyAsyncAdapter(BaseAsyncAdapter): async def get_json(self, *args, **kwargs): # pragma: no cover raise NotImplementedError async def get_text(self, *args, **kwargs): # pragma: no cover raise NotImplementedError class GetGeocoderTestCase(unittest.TestCase): def test_get_geocoder_for_service(self): assert get_geocoder_for_service("google") == GoogleV3 assert get_geocoder_for_service("googlev3") == GoogleV3 def test_get_geocoder_for_service_raises_for_unknown(self): with pytest.raises(GeocoderNotFound): get_geocoder_for_service("") class GeocoderTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.geocoder = Geocoder() def test_init_with_args(self): scheme = 'http' timeout = 942 proxies = {'https': '192.0.2.0'} user_agent = 'test app' ssl_context = sentinel.some_ssl_context adapter = DummySyncAdapter(proxies=None, ssl_context=None) geocoder = Geocoder( scheme=scheme, timeout=timeout, proxies=proxies, user_agent=user_agent, ssl_context=ssl_context, adapter_factory=lambda **kw: adapter, ) for attr in ('scheme', 'timeout', 'proxies', 'ssl_context'): assert locals()[attr] == getattr(geocoder, attr) assert user_agent == geocoder.headers['User-Agent'] assert adapter is geocoder.adapter @patch.object(geopy.geocoders.options, 'default_adapter_factory', DummySyncAdapter) def test_init_with_defaults(self): attr_to_option = { 'scheme': 'default_scheme', 'timeout': 'default_timeout', 'proxies': 'default_proxies', 'ssl_context': 'default_ssl_context', } geocoder = Geocoder() for geocoder_attr, options_attr in attr_to_option.items(): assert ( getattr(geopy.geocoders.options, options_attr) == getattr(geocoder, geocoder_attr) ) assert ( geopy.geocoders.options.default_user_agent == geocoder.headers['User-Agent'] ) assert DummySyncAdapter is type(geocoder.adapter) # noqa @patch.object(geopy.geocoders.options, 'default_proxies', {'https': '192.0.2.0'}) @patch.object(geopy.geocoders.options, 'default_timeout', 10) @patch.object(geopy.geocoders.options, 'default_ssl_context', sentinel.some_ssl_context) def test_init_with_none_overrides_default(self): geocoder = Geocoder(proxies=None, timeout=None, ssl_context=None) assert geocoder.proxies is None assert geocoder.timeout is None assert geocoder.ssl_context is None @patch.object(geopy.geocoders.options, 'default_user_agent', 'mocked_user_agent/0.0.0') def test_user_agent_default(self): geocoder = Geocoder() assert geocoder.headers['User-Agent'] == 'mocked_user_agent/0.0.0' def test_user_agent_custom(self): geocoder = Geocoder( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' @patch.object(geopy.geocoders.options, 'default_timeout', 12) def test_call_geocoder_timeout(self): url = 'spam://ham/eggs' g = Geocoder(adapter_factory=DummySyncAdapter) assert g.timeout == 12 with ExitStack() as stack: mock_get_json = stack.enter_context(patch.object(g.adapter, 'get_json')) g._call_geocoder(url, lambda res: res) args, kwargs = mock_get_json.call_args assert kwargs['timeout'] == 12 g._call_geocoder(url, lambda res: res, timeout=7) args, kwargs = mock_get_json.call_args assert kwargs['timeout'] == 7 g._call_geocoder(url, lambda res: res, timeout=None) args, kwargs = mock_get_json.call_args assert kwargs['timeout'] is None def test_ssl_context(self): with ExitStack() as stack: mock_adapter = stack.enter_context( patch.object( geopy.geocoders.base.options, 'default_adapter_factory', return_value=DummySyncAdapter(proxies=None, ssl_context=None), ) ) for ssl_context in (None, sentinel.some_ssl_context): Geocoder(ssl_context=ssl_context) args, kwargs = mock_adapter.call_args assert kwargs['ssl_context'] is ssl_context class GeocoderPointCoercionTestCase(unittest.TestCase): coordinates = (40.74113, -73.989656) coordinates_str = "40.74113,-73.989656" coordinates_address = "175 5th Avenue, NYC, USA" def setUp(self): self.method = Geocoder()._coerce_point_to_string def test_point(self): latlon = self.method(Point(*self.coordinates)) assert latlon == self.coordinates_str def test_tuple_of_floats(self): latlon = self.method(self.coordinates) assert latlon == self.coordinates_str def test_string(self): latlon = self.method(self.coordinates_str) assert latlon == self.coordinates_str def test_string_is_trimmed(self): coordinates_str_spaces = " %s , %s " % self.coordinates latlon = self.method(coordinates_str_spaces) assert latlon == self.coordinates_str def test_output_format_is_respected(self): expected = " %s %s " % self.coordinates[::-1] lonlat = self.method(self.coordinates_str, " %(lon)s %(lat)s ") assert lonlat == expected def test_address(self): with pytest.raises(ValueError): self.method(self.coordinates_address) class GeocoderFormatBoundingBoxTestCase(unittest.TestCase): def setUp(self): self.method = Geocoder()._format_bounding_box def test_string_raises(self): with pytest.raises(GeocoderQueryError): self.method("5,5,5,5") def test_list_of_1_raises(self): with pytest.raises(GeocoderQueryError): self.method([5]) # TODO maybe raise for `[5, 5]` too? def test_list_of_3_raises(self): with pytest.raises(GeocoderQueryError): self.method([5, 5, 5]) def test_list_of_4_raises(self): with pytest.raises(GeocoderQueryError): self.method([5, 5, 5, 5]) def test_list_of_5_raises(self): with pytest.raises(GeocoderQueryError): self.method([5, 5, 5, 5, 5]) def test_points(self): bbox = self.method([Point(50, 160), Point(30, 170)]) assert bbox == "30.0,160.0,50.0,170.0" def test_lists(self): bbox = self.method([[50, 160], [30, 170]]) assert bbox == "30.0,160.0,50.0,170.0" bbox = self.method([["50", "160"], ["30", "170"]]) assert bbox == "30.0,160.0,50.0,170.0" def test_strings(self): bbox = self.method(["50, 160", "30,170"]) assert bbox == "30.0,160.0,50.0,170.0" def test_output_format(self): bbox = self.method([Point(50, 160), Point(30, 170)], " %(lon2)s|%(lat2)s -- %(lat1)s|%(lon1)s ") assert bbox == " 170.0|50.0 -- 30.0|160.0 " @pytest.mark.parametrize("adapter_factory", [DummySyncAdapter, DummyAsyncAdapter]) async def test_synchronize_decorator_sync_simple(adapter_factory): geocoder = Geocoder(adapter_factory=adapter_factory) calls = [] @_synchronized def f(self, one, *, two): calls.append((one, two)) return 42 res = f(geocoder, 1, two=2) if adapter_factory is DummyAsyncAdapter: res = await res assert 42 == res assert calls == [(1, 2)] async def test_synchronize_decorator_async_simple(): geocoder = Geocoder(adapter_factory=DummyAsyncAdapter) calls = [] @_synchronized def f(self, one, *, two): async def coro(): calls.append((one, two)) return 42 return coro() assert 42 == await f(geocoder, 1, two=2) assert calls == [(1, 2)] @pytest.mark.parametrize("adapter_factory", [DummySyncAdapter, DummyAsyncAdapter]) async def test_synchronize_decorator_sync_exception(adapter_factory): geocoder = Geocoder(adapter_factory=adapter_factory) @_synchronized def f(self, one, *, two): raise RuntimeError("test") with pytest.raises(RuntimeError): res = f(geocoder, 1, two=2) if adapter_factory is DummyAsyncAdapter: await res async def test_synchronize_decorator_async_exception(): geocoder = Geocoder(adapter_factory=DummyAsyncAdapter) @_synchronized def f(self, one, *, two): async def coro(): raise RuntimeError("test") return coro() with pytest.raises(RuntimeError): await f(geocoder, 1, two=2) @pytest.mark.parametrize("adapter_factory", [DummySyncAdapter, DummyAsyncAdapter]) async def test_synchronize_decorator_sync_reentrance(adapter_factory): calls = [] class DummyGeocoder(Geocoder): @_synchronized def f(self, i=0): calls.append(i) if len(calls) < 5: return self.f(i + 1) return 42 geocoder = DummyGeocoder(adapter_factory=adapter_factory) res = geocoder.f() if adapter_factory is DummyAsyncAdapter: res = await res assert 42 == res assert calls == list(range(5)) async def test_synchronize_decorator_async_reentrance(): calls = [] class DummyGeocoder(Geocoder): @_synchronized def f(self, i=0): async def coro(): calls.append(i) if len(calls) < 5: return await self.f(i + 1) return 42 return coro() geocoder = DummyGeocoder(adapter_factory=DummyAsyncAdapter) assert 42 == await geocoder.f() assert calls == list(range(5)) geopy-2.2.0/test/geocoders/bing.py0000644000076500000240000000615114072442760017526 0ustar kostyastaff00000000000000from geopy.geocoders import Bing from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestUnitBing: def test_user_agent_custom(self): geocoder = Bing( api_key='DUMMYKEY1234', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestBing(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return Bing( api_key=env['BING_KEY'], **kwargs ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u043c\u043e\u0441\u043a\u0432\u0430"}, {"latitude": 55.756, "longitude": 37.615}, ) async def test_reverse_point(self): await self.reverse_run( {"query": Point(40.753898, -73.985071)}, {"latitude": 40.753, "longitude": -73.984}, ) async def test_reverse_with_culture_de(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "culture": "DE"}, {}, ) assert "Vereinigte Staaten von Amerika" in res.address async def test_reverse_with_culture_en(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "culture": "EN"}, {}, ) assert "United States" in res.address async def test_reverse_with_include_country_code(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "include_country_code": True}, {}, ) assert res.raw["address"].get("countryRegionIso2", 'missing') == 'US' async def test_user_location(self): pennsylvania = (40.98327, -74.96064) colorado = (40.160, -87.784) pennsylvania_bias = (40.922351, -75.096562) colorado_bias = (39.914231, -105.070104) for expected, bias in ((pennsylvania, pennsylvania_bias), (colorado, colorado_bias)): await self.geocode_run( {"query": "20 Main Street", "user_location": Point(bias)}, {"latitude": expected[0], "longitude": expected[1], "delta": 3.0}, ) async def test_optional_params(self): res = await self.geocode_run( {"query": "Badeniho 1, Prague, Czech Republic", "culture": 'cs', "include_neighborhood": True, "include_country_code": True}, {}, ) address = res.raw['address'] assert address['neighborhood'] == "Praha 6" assert address['countryRegionIso2'] == "CZ" async def test_structured_query(self): res = await self.geocode_run( {"query": {'postalCode': '80020', 'countryRegion': 'United States'}}, {}, ) address = res.raw['address'] assert address['locality'] == "Broomfield" geopy-2.2.0/test/geocoders/databc.py0000644000076500000240000000310413700135050020004 0ustar kostyastaff00000000000000import pytest from geopy.exc import GeocoderQueryError from geopy.geocoders import DataBC from test.geocoders.util import BaseTestGeocoder class TestDataBC(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return DataBC(**kwargs) async def test_user_agent_custom(self): geocoder = DataBC( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_geocode(self): await self.geocode_run( {"query": "135 North Pym Road, Parksville"}, {"latitude": 49.321, "longitude": -124.337}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "Barri\u00e8re"}, {"latitude": 51.179, "longitude": -120.123}, ) async def test_multiple_results(self): res = await self.geocode_run( {"query": "1st St", "exactly_one": False}, {}, ) assert len(res) > 1 async def test_optional_params(self): await self.geocode_run( {"query": "5670 malibu terrace nanaimo bc", "location_descriptor": "accessPoint", "set_back": 100}, {"latitude": 49.2299, "longitude": -124.0163}, ) async def test_query_error(self): with pytest.raises(GeocoderQueryError): await self.geocode_run( {"query": "1 Main St, Vancouver", "location_descriptor": "access_Point"}, {}, expect_failure=True, ) geopy-2.2.0/test/geocoders/geocodeearth.py0000644000076500000240000000047314032167233021234 0ustar kostyastaff00000000000000from geopy.geocoders import GeocodeEarth from test.geocoders.pelias import BaseTestPelias from test.geocoders.util import env class TestGeocodeEarth(BaseTestPelias): @classmethod def make_geocoder(cls, **kwargs): return GeocodeEarth(env['GEOCODEEARTH_KEY'], **kwargs) geopy-2.2.0/test/geocoders/geocodio.py0000644000076500000240000000465514032207437020402 0ustar kostyastaff00000000000000import pytest from geopy import exc from geopy.geocoders import Geocodio from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestGeocodio(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return Geocodio(api_key=env['GEOCODIO_KEY'], **kwargs) async def test_user_agent_custom(self): geocoder = self.make_geocoder(user_agent='my_user_agent/1.0') assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_error_with_only_street(self): with pytest.raises(exc.GeocoderQueryError): await self.geocode_run( {'query': {'street': '435 north michigan ave'}}, {}, ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.89037, "longitude": -87.623192}, ) async def test_geocode_from_components(self): await self.geocode_run( { "query": { "street": "435 north michigan ave", "city": "chicago", "state": "IL", "postal_code": "60611" }, }, {"latitude": 41.89037, "longitude": -87.623192}, ) async def test_geocode_many_results(self): result = await self.geocode_run( {"query": "Springfield", "exactly_one": False}, {} ) assert len(result) > 1 async def test_reverse(self): location = await self.reverse_run( {"query": Point(40.75376406311989, -73.98489005863667)}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667}, ) assert "new york" in location.address.lower() async def test_geocode_no_result(self): await self.geocode_run( {"query": "dksajdkjashdkjashdjasghd"}, {}, expect_failure=True, ) async def test_geocode_structured_no_result(self): await self.geocode_run( {"query": {"city": "dkasdjksahdksajhd", "street": "sdahaskjdhask"}}, {}, expect_failure=True, ) async def test_reverse_no_result(self): await self.reverse_run( # North Atlantic Ocean {"query": (35.173809, -37.485351)}, {}, expect_failure=True, ) geopy-2.2.0/test/geocoders/geolake.py0000644000076500000240000000445414032167233020215 0ustar kostyastaff00000000000000from geopy.geocoders import Geolake from test.geocoders.util import BaseTestGeocoder, env class TestUnitGeolake: def test_user_agent_custom(self): geocoder = Geolake( api_key='DUMMYKEY1234', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestGeolake(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return Geolake( api_key=env['GEOLAKE_KEY'], timeout=10, **kwargs ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890344, "longitude": -87.623234, "address": "Chicago, US"}, ) async def test_geocode_country_codes_str(self): await self.geocode_run( {"query": "Toronto", "country_codes": "CA"}, {"latitude": 43.72, "longitude": -79.47, "address": "Toronto, CA"}, ) await self.geocode_run( {"query": "Toronto", "country_codes": "RU"}, {}, expect_failure=True ) async def test_geocode_country_codes_list(self): await self.geocode_run( {"query": "Toronto", "country_codes": ["CA", "RU"]}, {"latitude": 43.72, "longitude": -79.47, "address": "Toronto, CA"}, ) await self.geocode_run( {"query": "Toronto", "country_codes": ["UA", "RU"]}, {}, expect_failure=True ) async def test_geocode_structured(self): query = { "street": "north michigan ave", "houseNumber": "435", "city": "chicago", "state": "il", "zipcode": 60611, "country": "US" } await self.geocode_run( {"query": query}, {"latitude": 41.890344, "longitude": -87.623234} ) async def test_geocode_empty_result(self): await self.geocode_run( {"query": "xqj37"}, {}, expect_failure=True ) async def test_geocode_missing_city_in_result(self): await self.geocode_run( {"query": "H1W 0B4"}, {"latitude": 45.544952, "longitude": -73.546694, "address": "CA"} ) geopy-2.2.0/test/geocoders/geonames.py0000644000076500000240000001475514032167233020411 0ustar kostyastaff00000000000000import uuid import pytest from geopy import Point from geopy.exc import GeocoderAuthenticationFailure, GeocoderQueryError from geopy.geocoders import GeoNames from test.geocoders.util import BaseTestGeocoder, env try: import pytz pytz_available = True except ImportError: pytz_available = False class TestUnitGeoNames: def test_user_agent_custom(self): geocoder = GeoNames( username='DUMMYUSER_NORBERT', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestGeoNames(BaseTestGeocoder): delta = 0.04 @classmethod def make_geocoder(cls, **kwargs): return GeoNames(username=env['GEONAMES_USERNAME'], **kwargs) async def test_unicode_name(self): await self.geocode_run( {"query": "Mount Everest, Nepal"}, {"latitude": 27.987, "longitude": 86.925}, skiptest_on_failure=True, # sometimes the result is empty ) async def test_query_urlencoding(self): location = await self.geocode_run( {"query": "Ry\u016b\u014d"}, {"latitude": 35.65, "longitude": 138.5}, skiptest_on_failure=True, # sometimes the result is empty ) assert "Ry\u016b\u014d" in location.address async def test_reverse(self): location = await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", }, { "latitude": 40.75376406311989, "longitude": -73.98489005863667, }, ) assert "Times Square" in location.address async def test_geocode_empty_response(self): await self.geocode_run( {"query": "sdlahaslkhdkasldhkjsahdlkash"}, {}, expect_failure=True, ) async def test_reverse_nearby_place_name_raises_for_feature_code(self): with pytest.raises(ValueError): await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "feature_code": "ADM1", }, {}, ) with pytest.raises(ValueError): await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "feature_code": "ADM1", "find_nearby_type": "findNearbyPlaceName", }, {}, ) async def test_reverse_nearby_place_name_lang(self): location = await self.reverse_run( { "query": "52.50, 13.41", "lang": 'ru', }, {}, ) assert 'Берлин, Германия' in location.address async def test_reverse_find_nearby_raises_for_lang(self): with pytest.raises(ValueError): await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "find_nearby_type": 'findNearby', "lang": 'en', }, {}, ) async def test_reverse_find_nearby(self): location = await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "find_nearby_type": 'findNearby', }, { "latitude": 40.75376406311989, "longitude": -73.98489005863667, }, ) assert "New York, United States" in location.address async def test_reverse_find_nearby_feature_code(self): await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "find_nearby_type": 'findNearby', "feature_code": "ADM1", }, { "latitude": 40.16706, "longitude": -74.49987, }, ) async def test_reverse_raises_for_unknown_find_nearby_type(self): with pytest.raises(GeocoderQueryError): await self.reverse_run( { "query": "40.75376406311989, -73.98489005863667", "find_nearby_type": "findSomethingNonExisting", }, {}, ) @pytest.mark.skipif("not pytz_available") async def test_reverse_timezone(self): new_york_point = Point(40.75376406311989, -73.98489005863667) america_new_york = pytz.timezone("America/New_York") timezone = await self.reverse_timezone_run( {"query": new_york_point}, america_new_york, ) assert timezone.raw['countryCode'] == 'US' @pytest.mark.skipif("not pytz_available") async def test_reverse_timezone_unknown(self): await self.reverse_timezone_run( # Geonames doesn't return `timezoneId` for Antarctica, # but it provides GMT offset which can be used # to create a FixedOffset pytz timezone. {"query": "89.0, 1.0"}, pytz.UTC, ) await self.reverse_timezone_run( {"query": "89.0, 80.0"}, pytz.FixedOffset(5 * 60), ) async def test_country_str(self): await self.geocode_run( {"query": "kazan", "country": "TR"}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_country_list(self): await self.geocode_run( {"query": "kazan", "country": ["CN", "TR", "JP"]}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_country_bias(self): await self.geocode_run( {"query": "kazan", "country_bias": "TR"}, {"latitude": 40.2317, "longitude": 32.6839}, ) class TestGeoNamesInvalidAccount(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return GeoNames( username="geopy-not-existing-%s" % uuid.uuid4(), **kwargs ) async def test_geocode(self): with pytest.raises(GeocoderAuthenticationFailure): await self.geocode_run( {"query": "moscow"}, {}, expect_failure=True, ) @pytest.mark.skipif("not pytz_available") async def test_reverse_timezone(self): with pytest.raises(GeocoderAuthenticationFailure): await self.reverse_timezone_run( {"query": "40.6997716, -73.9753359"}, None, ) geopy-2.2.0/test/geocoders/googlev3.py0000644000076500000240000002516414032167233020334 0ustar kostyastaff00000000000000import base64 from datetime import datetime from urllib.parse import parse_qs, urlparse import pytest from geopy import exc from geopy.geocoders import GoogleV3 from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env try: import pytz pytz_available = True except ImportError: pytz_available = False class TestGoogleV3(BaseTestGeocoder): new_york_point = Point(40.75376406311989, -73.98489005863667) @classmethod def make_geocoder(cls, **kwargs): return GoogleV3(api_key=env['GOOGLE_KEY'], **kwargs) async def test_user_agent_custom(self): geocoder = GoogleV3( api_key='mock', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_configuration_error(self): with pytest.raises(exc.ConfigurationError): GoogleV3(api_key='mock', client_id='a') with pytest.raises(exc.ConfigurationError): GoogleV3(api_key='mock', secret_key='a') async def test_error_with_no_api_key(self): with pytest.raises(exc.ConfigurationError): GoogleV3() async def test_no_error_with_no_api_key_but_using_premier(self): GoogleV3(client_id='client_id', secret_key='secret_key') async def test_check_status(self): assert self.geocoder._check_status("ZERO_RESULTS") is None with pytest.raises(exc.GeocoderQuotaExceeded): self.geocoder._check_status("OVER_QUERY_LIMIT") with pytest.raises(exc.GeocoderQueryError): self.geocoder._check_status("REQUEST_DENIED") with pytest.raises(exc.GeocoderQueryError): self.geocoder._check_status("INVALID_REQUEST") with pytest.raises(exc.GeocoderQueryError): self.geocoder._check_status("_") async def test_get_signed_url(self): geocoder = GoogleV3( api_key='mock', client_id='my_client_id', secret_key=base64.urlsafe_b64encode('my_secret_key'.encode('utf8')) ) assert geocoder.premier # the two possible URLs handle both possible orders of the request # params; because it's unordered, either is possible, and each has # its own hash assert geocoder._get_signed_url( {'address': '1 5th Ave New York, NY'} ) in ( "https://maps.googleapis.com/maps/api/geocode/json?" "address=1+5th+Ave+New+York%2C+NY&client=my_client_id&" "signature=Z_1zMBa3Xu0W4VmQfaBR8OQMnDM=", "https://maps.googleapis.com/maps/api/geocode/json?" "client=my_client_id&address=1+5th+Ave+New+York%2C+NY&" "signature=D3PL0cZJrJYfveGSNoGqrrMsz0M=" ) async def test_get_signed_url_with_channel(self): geocoder = GoogleV3( api_key='mock', client_id='my_client_id', secret_key=base64.urlsafe_b64encode('my_secret_key'.encode('utf8')), channel='my_channel' ) signed_url = geocoder._get_signed_url({'address': '1 5th Ave New York, NY'}) params = parse_qs(urlparse(signed_url).query) assert 'channel' in params assert 'signature' in params assert 'client' in params async def test_format_components_param(self): f = self.geocoder._format_components_param assert f({}) == '' assert f([]) == '' assert f({'country': 'FR'}) == 'country:FR' output = f({'administrative_area': 'CA', 'country': 'FR'}) # the order the dict is iterated over is not important assert output in ( 'administrative_area:CA|country:FR', 'country:FR|administrative_area:CA' ), output assert f([('country', 'FR')]) == 'country:FR' output = f([ ('administrative_area', 'CA'), ('administrative_area', 'Los Angeles'), ('country', 'US') ]) assert ( output == 'administrative_area:CA|administrative_area:Los Angeles|country:US' ) with pytest.raises(ValueError): f(None) with pytest.raises(ValueError): f('administrative_area:CA|country:FR') async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab"}, {"latitude": 39.916, "longitude": 116.390}, ) async def test_geocode_with_conflicting_components(self): await self.geocode_run( { "query": "santa cruz", "components": { "administrative_area": "CA", "country": "FR" } }, {}, expect_failure=True ) await self.geocode_run( { "query": "santa cruz", "components": [ ('administrative_area', 'CA'), ('country', 'FR') ] }, {}, expect_failure=True ) async def test_components(self): await self.geocode_run( { "query": "santa cruz", "components": { "country": "ES" } }, {"latitude": 28.4636296, "longitude": -16.2518467}, ) await self.geocode_run( { "query": "santa cruz", "components": [ ('country', 'ES') ] }, {"latitude": 28.4636296, "longitude": -16.2518467}, ) async def test_components_without_query(self): await self.geocode_run( { "components": {"city": "Paris", "country": "FR"}, }, {"latitude": 46.227638, "longitude": 2.213749}, ) await self.geocode_run( { "components": [("city", "Paris"), ("country", "FR")], }, {"latitude": 46.227638, "longitude": 2.213749}, ) async def test_reverse(self): await self.reverse_run( {"query": self.new_york_point}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667}, ) async def test_zero_results(self): with pytest.raises(exc.GeocoderQueryError): await self.geocode_run( {"query": ''}, {}, expect_failure=True, ) @pytest.mark.skipif("not pytz_available") async def test_timezone_datetime(self): await self.reverse_timezone_run( {"query": self.new_york_point, "at_time": datetime.utcfromtimestamp(0)}, pytz.timezone("America/New_York"), ) @pytest.mark.skipif("not pytz_available") async def test_timezone_at_time_normalization(self): utc_naive_dt = datetime(2010, 1, 1, 0, 0, 0) utc_timestamp = 1262304000 assert ( utc_timestamp == self.geocoder._normalize_timezone_at_time(utc_naive_dt) ) assert ( utc_timestamp < self.geocoder._normalize_timezone_at_time(None) ) tz = pytz.timezone("Etc/GMT-2") local_aware_dt = tz.localize(datetime(2010, 1, 1, 2, 0, 0)) assert( utc_timestamp == self.geocoder._normalize_timezone_at_time(local_aware_dt) ) @pytest.mark.skipif("not pytz_available") async def test_timezone_integer_raises(self): # In geopy 1.x `at_time` could be an integer -- a unix timestamp. # This is an error since geopy 2.0. with pytest.raises(exc.GeocoderQueryError): await self.reverse_timezone_run( {"query": self.new_york_point, "at_time": 0}, pytz.timezone("America/New_York"), ) @pytest.mark.skipif("not pytz_available") async def test_timezone_no_date(self): await self.reverse_timezone_run( {"query": self.new_york_point}, pytz.timezone("America/New_York"), ) @pytest.mark.skipif("not pytz_available") async def test_timezone_invalid_at_time(self): with pytest.raises(exc.GeocoderQueryError): self.geocoder.reverse_timezone(self.new_york_point, at_time="eek") @pytest.mark.skipif("not pytz_available") async def test_reverse_timezone_unknown(self): await self.reverse_timezone_run( # Google doesn't return a timezone for Antarctica. {"query": "89.0, 1.0"}, None, ) async def test_geocode_bounds(self): await self.geocode_run( {"query": "221b Baker St", "bounds": [[50, -2], [55, 2]]}, {"latitude": 51.52, "longitude": -0.15}, ) async def test_geocode_bounds_invalid(self): with pytest.raises(exc.GeocoderQueryError): await self.geocode_run( {"query": "221b Baker St", "bounds": [50, -2, 55]}, {"latitude": 51.52, "longitude": -0.15}, ) async def test_geocode_place_id_invalid(self): await self.geocode_run( {"place_id": "ChIJOcfP0Iq2j4ARDrXUa7ZWs34"}, {"latitude": 37.22, "longitude": -122.05} ) async def test_geocode_place_id_not_invalid(self): with pytest.raises(exc.GeocoderQueryError): await self.geocode_run( {"place_id": "xxxxx"}, {}, expect_failure=True, ) async def test_place_id_zero_result(self): with pytest.raises(exc.GeocoderQueryError): await self.geocode_run( {"place_id": ""}, {}, expect_failure=True, ) async def test_geocode_place_id_with_query(self): with pytest.raises(ValueError): await self.geocode_run( {"place_id": "ChIJOcfP0Iq2j4ARDrXUa7ZWs34", "query": "silicon valley"}, {} ) async def test_geocode_place_id_with_bounds(self): with pytest.raises(ValueError): await self.geocode_run( {"place_id": "ChIJOcfP0Iq2j4ARDrXUa7ZWs34", "bounds": [50, -2, 55, 2]}, {} ) async def test_geocode_place_id_with_query_and_bounds(self): with pytest.raises(ValueError): await self.geocode_run( {"place_id": "ChIJOcfP0Iq2j4ARDrXUa7ZWs34", "query": "silicon valley", "bounds": [50, -2, 55, 2]}, {} ) geopy-2.2.0/test/geocoders/here.py0000644000076500000240000002401614034374314017527 0ustar kostyastaff00000000000000import warnings import pytest from geopy import exc from geopy.geocoders import Here, HereV7 from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestUnitHere: def test_user_agent_custom(self): geocoder = Here( apikey='DUMMYKEY1234', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' def test_error_with_no_keys(self): with pytest.raises(exc.ConfigurationError): Here() def test_warning_with_legacy_auth(self): with warnings.catch_warnings(record=True) as w: Here( app_id='DUMMYID1234', app_code='DUMMYCODE1234', ) assert len(w) == 1 def test_no_warning_with_apikey(self): with warnings.catch_warnings(record=True) as w: Here( apikey='DUMMYKEY1234', ) assert len(w) == 0 class BaseTestHere(BaseTestGeocoder): async def test_geocode_empty_result(self): await self.geocode_run( {"query": "xqj37"}, {}, expect_failure=True ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624} ) async def test_geocode_structured(self): query = { "street": "north michigan ave", "housenumber": "435", "city": "chicago", "state": "il", "postalcode": 60611, "country": "usa" } await self.geocode_run( {"query": query}, {"latitude": 41.890, "longitude": -87.624} ) async def test_geocode_unicode_name(self): # unicode in Japanese for Paris. (POIs not included.) await self.geocode_run( {"query": "\u30d1\u30ea"}, {"latitude": 48.85718, "longitude": 2.34141} ) async def test_bbox(self): await self.geocode_run( {"query": "moscow", # Idaho USA "bbox": [[50.1, -130.1], [44.1, -100.9]]}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_mapview(self): await self.geocode_run( {"query": "moscow", # Idaho USA "mapview": [[50.1, -130.1], [44.1, -100.9]]}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_geocode_shapes(self): address_string = "435 north michigan ave, chicago il 60611 usa" res = await self.geocode_run( {"query": address_string, "additional_data": "IncludeShapeLevel,postalCode"}, {"latitude": 41.89035, "longitude": -87.62333}, ) shape_value = res.raw['Location']['Shape']['Value'] assert shape_value.startswith('MULTIPOLYGON (((') async def test_geocode_with_language_de(self): address_string = "435 north michigan ave, chicago il 60611 usa" res = await self.geocode_run( {"query": address_string, "language": "de-DE"}, {} ) assert "Vereinigte Staaten" in res.address async def test_geocode_with_language_en(self): address_string = "435 north michigan ave, chicago il 60611 usa" res = await self.geocode_run( {"query": address_string, "language": "en-US"}, {} ) assert "United States" in res.address async def test_geocode_with_paging(self): address_string = "Hauptstr., Berlin, Germany" input = {"query": address_string, "maxresults": 12, "exactly_one": False} res = await self.geocode_run(input, {}) assert len(res) == 12 input["pageinformation"] = 2 res = await self.geocode_run(input, {}) assert len(res) >= 3 assert len(res) <= 6 input["pageinformation"] = 3 res = await self.geocode_run(input, {}, expect_failure=True) async def test_reverse(self): await self.reverse_run( {"query": Point(40.753898, -73.985071)}, {"latitude": 40.753898, "longitude": -73.985071} ) async def test_reverse_point_radius_1000_float(self): # needs more testing res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "radius": 1000.12, "exactly_one": False}, {"latitude": 40.753898, "longitude": -73.985071} ) assert len(res) > 5 async def test_reverse_point_radius_10(self): # needs more testing res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "radius": 10, "exactly_one": False}, {"latitude": 40.753898, "longitude": -73.985071} ) assert len(res) > 5 async def test_reverse_with_language_de(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "language": "de-DE"}, {} ) assert "Vereinigte Staaten" in res.address async def test_reverse_with_language_en(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "language": "en-US"}, {} ) assert "United States" in res.address async def test_reverse_with_mode_areas(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "mode": "retrieveAreas"}, {} ) assert "Theater District-Times Square" in res.address async def test_reverse_with_maxresults_5(self): res = await self.reverse_run( { "query": Point(40.753898, -73.985071), "maxresults": 5, "exactly_one": False }, {} ) assert len(res) == 5 class TestHereApiKey(BaseTestHere): @classmethod def make_geocoder(cls, **kwargs): return Here( apikey=env['HERE_APIKEY'], timeout=10, **kwargs ) class TestHereLegacyAuth(BaseTestHere): @classmethod def make_geocoder(cls, **kwargs): with warnings.catch_warnings(record=True) as w: geocoder = Here( app_id=env['HERE_APP_ID'], app_code=env['HERE_APP_CODE'], timeout=10, **kwargs ) assert len(w) == 1 return geocoder class TestHereV7(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return HereV7(env['HERE_APIKEY'], **kwargs) async def test_geocode_empty_result(self): await self.geocode_run( {"query": "xqj37"}, {}, expect_failure=True ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624} ) async def test_geocode_query_and_components(self): query = "435 north michigan ave" components = { "city": "chicago", "state": "il", "postalCode": 60611, "country": "usa", } await self.geocode_run( {"query": query, "components": components}, {"latitude": 41.890, "longitude": -87.624} ) async def test_geocode_structured(self): components = { "street": "north michigan ave", "houseNumber": "435", "city": "chicago", "state": "il", "postalCode": 60611, "country": "usa", } await self.geocode_run( {"components": components}, {"latitude": 41.890, "longitude": -87.624} ) async def test_geocode_unicode_name(self): # unicode in Japanese for Paris. (POIs not included.) await self.geocode_run( {"query": "\u30d1\u30ea"}, {"latitude": 48.85718, "longitude": 2.34141} ) async def test_geocode_at(self): await self.geocode_run( { "query": "moscow", # Idaho USA "at": (46.734303, -116.999558) }, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_geocode_countries(self): await self.geocode_run( { "query": "moscow", # Idaho USA "countries": ["USA", "CAN"], }, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_geocode_language(self): address_string = "435 north michigan ave, chicago il 60611 usa" res = await self.geocode_run( {"query": address_string, "language": "de-DE"}, {} ) assert "Vereinigte Staaten" in res.address res = await self.geocode_run( {"query": address_string, "language": "en-US"}, {} ) assert "United States" in res.address async def test_geocode_limit(self): res = await self.geocode_run( { "query": "maple street", "limit": 5, "exactly_one": False }, {} ) assert len(res) == 5 async def test_reverse(self): await self.reverse_run( {"query": Point(40.753898, -73.985071)}, {"latitude": 40.753898, "longitude": -73.985071} ) async def test_reverse_language(self): res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "language": "de-DE"}, {} ) assert "Vereinigte Staaten" in res.address res = await self.reverse_run( {"query": Point(40.753898, -73.985071), "language": "en-US"}, {} ) assert "United States" in res.address async def test_reverse_limit(self): res = await self.reverse_run( { "query": Point(40.753898, -73.985071), "limit": 5, "exactly_one": False }, {} ) assert len(res) == 5 geopy-2.2.0/test/geocoders/ignfrance.py0000644000076500000240000002132414032167233020535 0ustar kostyastaff00000000000000import pytest from async_generator import async_generator, yield_ from geopy.exc import ConfigurationError, GeocoderQueryError from geopy.geocoders import IGNFrance from test.geocoders.util import BaseTestGeocoder, env from test.proxy_server import ProxyServerThread class TestUnitIGNFrance: def test_user_agent_custom(self): geocoder = IGNFrance( api_key='DUMMYKEY1234', username='MUSTERMANN', password='tops3cr3t', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' def test_invalid_auth_1(self): with pytest.raises(ConfigurationError): IGNFrance(api_key="a") def test_invalid_auth_2(self): with pytest.raises(ConfigurationError): IGNFrance(api_key="a", username="b", referer="c") def test_invalid_auth_3(self): with pytest.raises(ConfigurationError): IGNFrance(api_key="a", username="b") class BaseTestIGNFrance(BaseTestGeocoder): async def test_invalid_query_type(self): with pytest.raises(GeocoderQueryError): self.geocoder.geocode("44109000EX0114", query_type="invalid") async def test_invalid_query_parcel(self): with pytest.raises(GeocoderQueryError): self.geocoder.geocode( "incorrect length string", query_type="CadastralParcel", ) async def test_geocode(self): await self.geocode_run( {"query": "44109000EX0114", "query_type": "CadastralParcel"}, {"latitude": 47.222482, "longitude": -1.556303}, ) async def test_geocode_no_result(self): await self.geocode_run( {"query": 'asdfasdfasdf'}, {}, expect_failure=True, ) async def test_reverse_no_result(self): await self.reverse_run( # North Atlantic Ocean {"query": (35.173809, -37.485351)}, {}, expect_failure=True ) async def test_geocode_with_address(self): await self.geocode_run( {"query": "Camp des Landes, 41200 VILLEFRANCHE-SUR-CHER", "query_type": "StreetAddress"}, {"latitude": 47.293048, "longitude": 1.718985, "address": "le camp des landes, 41200 Villefranche-sur-Cher"}, ) async def test_geocode_freeform(self): await self.geocode_run( {"query": "8 rue Général Buat, Nantes", "query_type": "StreetAddress", "is_freeform": True}, {"address": "8 r general buat , 44000 Nantes"}, ) async def test_geocode_position_of_interest(self): res = await self.geocode_run( {"query": "Chambéry", "query_type": "PositionOfInterest", "exactly_one": False}, {}, ) addresses = [location.address for location in res] assert "02000 Chambry" in addresses assert "16420 Saint-Christophe" in addresses async def test_geocode_filter_by_attribute(self): res = await self.geocode_run( {"query": "Les Molettes", "query_type": "PositionOfInterest", "maximum_responses": 10, "filtering": '38', "exactly_one": False}, {}, ) departements = [location.raw['departement'] for location in res] unique = list(set(departements)) assert len(unique) == 1 assert unique[0] == "38" async def test_geocode_filter_by_envelope(self): lat_min, lng_min, lat_max, lng_max = 45.00, 5, 46, 6.40 spatial_filtering_envelope = """ {lat_min} {lng_min} {lat_max} {lng_max} """.format( lat_min=lat_min, lng_min=lng_min, lat_max=lat_max, lng_max=lng_max ) res_spatial_filter = await self.geocode_run( {"query": 'Les Molettes', "query_type": 'PositionOfInterest', "maximum_responses": 10, "filtering": spatial_filtering_envelope, "exactly_one": False}, {}, ) departements_spatial = list( {i.raw['departement'] for i in res_spatial_filter} ) res_no_spatial_filter = await self.geocode_run( {"query": 'Les Molettes', "query_type": 'PositionOfInterest', "maximum_responses": 10, "exactly_one": False}, {}, ) departements_no_spatial = list( set([ i.raw['departement'] for i in res_no_spatial_filter ]) ) assert len(departements_no_spatial) > len(departements_spatial) async def test_reverse(self): res = await self.reverse_run( {"query": '47.229554,-1.541519'}, {}, ) assert res.address == '7 av camille guerin, 44000 Nantes' async def test_reverse_invalid_preference(self): with pytest.raises(GeocoderQueryError): self.geocoder.reverse( query='47.229554,-1.541519', reverse_geocode_preference=['a'] # invalid ) async def test_reverse_preference(self): res = await self.reverse_run( {"query": '47.229554,-1.541519', "exactly_one": False, "reverse_geocode_preference": ['StreetAddress', 'PositionOfInterest']}, {}, ) addresses = [location.address for location in res] assert "3 av camille guerin, 44000 Nantes" in addresses assert "5 av camille guerin, 44000 Nantes" in addresses async def test_reverse_by_radius(self): spatial_filtering_radius = """ {coord} {radius} """.format(coord='48.8033333 2.3241667', radius='50') res_call_radius = await self.reverse_run( {"query": '48.8033333,2.3241667', "exactly_one": False, "maximum_responses": 10, "filtering": spatial_filtering_radius}, {}, ) res_call = await self.reverse_run( {"query": '48.8033333,2.3241667', "exactly_one": False, "maximum_responses": 10}, {}, ) coordinates_couples_radius = set([ (str(location.latitude) + ' ' + str(location.longitude)) for location in res_call_radius ]) coordinates_couples = set([ (str(location.latitude) + ' ' + str(location.longitude)) for location in res_call ]) assert coordinates_couples_radius.issubset(coordinates_couples) class TestIGNFranceApiKeyAuth(BaseTestIGNFrance): @classmethod def make_geocoder(cls, **kwargs): return IGNFrance( api_key=env['IGNFRANCE_KEY'], referer=env['IGNFRANCE_REFERER'], timeout=10 ) class TestIGNFranceUsernameAuth(BaseTestIGNFrance): @classmethod def make_geocoder(cls, **kwargs): return IGNFrance( api_key=env['IGNFRANCE_USERNAME_KEY'], username=env['IGNFRANCE_USERNAME'], password=env['IGNFRANCE_PASSWORD'], timeout=10, **kwargs ) class TestIGNFranceUsernameAuthProxy(BaseTestGeocoder): proxy_timeout = 5 @classmethod def make_geocoder(cls, **kwargs): return IGNFrance( api_key=env['IGNFRANCE_USERNAME_KEY'], username=env['IGNFRANCE_USERNAME'], password=env['IGNFRANCE_PASSWORD'], timeout=10, **kwargs ) @pytest.fixture(scope='class', autouse=True) @async_generator async def start_proxy(_, request, class_geocoder): cls = request.cls cls.proxy_server = ProxyServerThread(timeout=cls.proxy_timeout) cls.proxy_server.start() cls.proxy_url = cls.proxy_server.get_proxy_url() async with cls.inject_geocoder(cls.make_geocoder(proxies=cls.proxy_url)): await yield_() cls.proxy_server.stop() cls.proxy_server.join() async def test_proxy_is_respected(self): assert 0 == len(self.proxy_server.requests) await self.geocode_run( {"query": "Camp des Landes, 41200 VILLEFRANCHE-SUR-CHER", "query_type": "StreetAddress"}, {"latitude": 47.293048, "longitude": 1.718985, "address": "le camp des landes, 41200 Villefranche-sur-Cher"}, ) assert 1 == len(self.proxy_server.requests) geopy-2.2.0/test/geocoders/mapbox.py0000644000076500000240000000562514032167233020075 0ustar kostyastaff00000000000000import pytest from geopy.geocoders import MapBox from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestMapBox(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return MapBox(api_key=env['MAPBOX_KEY'], timeout=3, **kwargs) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab"}, {"latitude": 39.916, "longitude": 116.390}, ) async def test_reverse(self): new_york_point = Point(40.75376406311989, -73.98489005863667) location = await self.reverse_run( {"query": new_york_point}, {"latitude": 40.7537640, "longitude": -73.98489, "delta": 1}, ) assert "New York" in location.address async def test_zero_results(self): await self.geocode_run( {"query": 'asdfasdfasdf'}, {}, expect_failure=True, ) async def test_geocode_outside_bbox(self): await self.geocode_run( { "query": "435 north michigan ave, chicago il 60611 usa", "bbox": [[34.172684, -118.604794], [34.236144, -118.500938]] }, {}, expect_failure=True, ) async def test_geocode_bbox(self): await self.geocode_run( { "query": "435 north michigan ave, chicago il 60611 usa", "bbox": [Point(35.227672, -103.271484), Point(48.603858, -74.399414)] }, {"latitude": 41.890, "longitude": -87.624}, ) async def test_geocode_proximity(self): await self.geocode_run( {"query": "200 queen street", "proximity": Point(45.3, -66.1)}, {"latitude": 45.270208, "longitude": -66.050289, "delta": 0.1}, ) async def test_geocode_country_str(self): await self.geocode_run( {"query": "kazan", "country": "TR"}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_geocode_country_list(self): await self.geocode_run( {"query": "kazan", "country": ["CN", "TR"]}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_geocode_raw(self): result = await self.geocode_run({"query": "New York"}, {}) delta = 1.0 expected = pytest.approx((-73.8784155, 40.6930727), abs=delta) assert expected == result.raw['center'] async def test_geocode_exactly_one_false(self): list_result = await self.geocode_run( {"query": "maple street", "exactly_one": False}, {}, ) assert len(list_result) >= 3 geopy-2.2.0/test/geocoders/mapquest.py0000644000076500000240000000420014032167233020432 0ustar kostyastaff00000000000000from geopy.geocoders import MapQuest from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestMapQuest(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return MapQuest(api_key=env['MAPQUEST_KEY'], timeout=3, **kwargs) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.89036, "longitude": -87.624043}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab"}, {"latitude": 25.0968, "longitude": 121.54714, "delta": 5.0}, ) async def test_reverse(self): new_york_point = Point(40.75376406311989, -73.98489005863667) location = await self.reverse_run( {"query": new_york_point}, {"latitude": 40.7537640, "longitude": -73.98489, "delta": 1}, ) assert "New York" in location.address async def test_zero_results(self): await self.geocode_run( {"query": ''}, {}, expect_failure=True, ) async def test_geocode_bbox(self): await self.geocode_run( { "query": "435 north michigan ave, chicago il 60611 usa", "bounds": [Point(35.227672, -103.271484), Point(48.603858, -74.399414)] }, {"latitude": 41.890, "longitude": -87.624}, ) async def test_geocode_raw(self): result = await self.geocode_run( {"query": "New York"}, {"latitude": 40.713054, "longitude": -74.007228, "delta": 1}, ) assert result.raw['adminArea1'] == "US" async def test_geocode_limit(self): list_result = await self.geocode_run( {"query": "maple street", "exactly_one": False, "limit": 2}, {}, ) assert len(list_result) == 2 list_result = await self.geocode_run( {"query": "maple street", "exactly_one": False, "limit": 4}, {}, ) assert len(list_result) == 4 geopy-2.2.0/test/geocoders/maptiler.py0000644000076500000240000000643414032167233020423 0ustar kostyastaff00000000000000import pytest from geopy.geocoders import MapTiler from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class TestMapTiler(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return MapTiler(api_key=env['MAPTILER_KEY'], timeout=3, **kwargs) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "Stadelhoferstrasse 8, 8001 Z\u00fcrich"}, {"latitude": 47.36649, "longitude": 8.54855}, ) async def test_reverse(self): new_york_point = Point(40.75376406311989, -73.98489005863667) location = await self.reverse_run( {"query": new_york_point}, {"latitude": 40.7537640, "longitude": -73.98489, "delta": 1}, ) assert "New York" in location.address async def test_zero_results(self): await self.geocode_run( {"query": 'asdfasdfasdf'}, {}, expect_failure=True, ) async def test_geocode_outside_bbox(self): await self.geocode_run( { "query": "435 north michigan ave, chicago il 60611 usa", "bbox": [[34.172684, -118.604794], [34.236144, -118.500938]] }, {}, expect_failure=True, ) async def test_geocode_bbox(self): await self.geocode_run( { "query": "435 north michigan ave, chicago il 60611 usa", "bbox": [Point(35.227672, -103.271484), Point(48.603858, -74.399414)] }, {"latitude": 41.890, "longitude": -87.624}, ) async def test_geocode_proximity(self): await self.geocode_run( {"query": "200 queen street", "proximity": Point(45.3, -66.1)}, {"latitude": 44.038901, "longitude": -64.73052, "delta": 0.1}, ) async def test_reverse_language(self): zurich_point = Point(47.3723, 8.5422) location = await self.reverse_run( {"query": zurich_point, "language": "ja"}, {"latitude": 47.3723, "longitude": 8.5422, "delta": 1}, ) assert "\u30c1\u30e5\u30fc\u30ea\u30c3\u30d2" in location.address async def test_geocode_language(self): location = await self.geocode_run( {"query": "Z\u00fcrich", "language": "ja", "proximity": Point(47.3723, 8.5422)}, {"latitude": 47.3723, "longitude": 8.5422, "delta": 1}, ) assert "\u30c1\u30e5\u30fc\u30ea\u30c3\u30d2" in location.address async def test_geocode_raw(self): result = await self.geocode_run({"query": "New York"}, {}) delta = 1.0 expected = pytest.approx((-73.8784155, 40.6930727), abs=delta) assert expected == result.raw['center'] assert "relation175905" == result.raw['properties']['osm_id'] async def test_geocode_exactly_one_false(self): list_result = await self.geocode_run( {"query": "maple street", "exactly_one": False}, {}, ) assert len(list_result) >= 3 geopy-2.2.0/test/geocoders/nominatim.py0000644000076500000240000002700314072443441020576 0ustar kostyastaff00000000000000import warnings from unittest.mock import patch import pytest import geopy.geocoders from geopy.exc import ConfigurationError from geopy.geocoders import Nominatim from geopy.point import Point from test.geocoders.util import BaseTestGeocoder class BaseTestNominatim(BaseTestGeocoder): # Common test cases for Nominatim-based geocoders. # Assumes that Nominatim uses the OSM data. delta = 0.04 async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab \u5317\u4eac"}, {"latitude": 39.916, "longitude": 116.390, "delta": 1.0}, ) async def test_geocode_empty_result(self): await self.geocode_run( {"query": "dsadjkasdjasd"}, {}, expect_failure=True, ) async def test_reverse_empty_result(self): await self.reverse_run( {"query": Point(0.05, -0.15)}, {}, expect_failure=True, ) async def test_limit(self): with pytest.raises(ValueError): # non-positive limit await self.geocode_run( {"query": "does not matter", "limit": 0, "exactly_one": False}, {} ) result = await self.geocode_run( {"query": "second street", "limit": 4, "exactly_one": False}, {} ) assert len(result) >= 3 # PickPoint sometimes returns 3 assert 4 >= len(result) @patch.object(geopy.geocoders.options, 'default_user_agent', 'mocked_user_agent/0.0.0') def test_user_agent_default(self): geocoder = self.make_geocoder(user_agent=None) assert geocoder.headers['User-Agent'] == 'mocked_user_agent/0.0.0' async def test_user_agent_custom(self): geocoder = self.make_geocoder( user_agent='my_test_application' ) assert geocoder.headers['User-Agent'] == 'my_test_application' async def test_reverse(self): location = await self.reverse_run( {"query": Point(40.75376406311989, -73.98489005863667)}, {"latitude": 40.753, "longitude": -73.984} ) assert "New York" in location.address async def test_structured_query(self): await self.geocode_run( {"query": {"country": "us", "city": "moscow", "state": "idaho"}}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_city_district_with_dict_query(self): query = {'postalcode': 10117} result = await self.geocode_run( {"query": query, "addressdetails": True, "country_codes": "DE"}, {}, ) try: # For some queries `city_district` might be missing in the response. # For this specific query on OpenMapQuest the key is also missing. city_district = result.raw['address']['city_district'] except KeyError: # MapQuest city_district = result.raw['address']['suburb'] assert city_district == 'Mitte' async def test_geocode_language_parameter(self): query = "Mohrenstrasse Berlin" result_geocode = await self.geocode_run( {"query": query, "addressdetails": True, "language": "de"}, {}, ) assert result_geocode.raw['address']['country'] == "Deutschland" result_geocode = await self.geocode_run( {"query": query, "addressdetails": True, "language": "en"}, {}, ) assert result_geocode.raw['address']['country'] == "Germany" async def test_reverse_language_parameter(self): query = "52.51693903613385, 13.3859332733135" result_reverse_de = await self.reverse_run( {"query": query, "language": "de"}, {}, ) assert result_reverse_de.raw['address']['country'] == "Deutschland" result_reverse_en = await self.reverse_run( {"query": query, "language": "en"}, {}, ) # have had a change in the exact authority name assert "Germany" in result_reverse_en.raw['address']['country'] async def test_geocode_geometry_wkt(self): result_geocode = await self.geocode_run( {"query": "Halensee,Berlin", "geometry": 'WKT'}, {}, ) assert result_geocode.raw['geotext'].startswith('POLYGON((') async def test_geocode_geometry_svg(self): result_geocode = await self.geocode_run( {"query": "Halensee,Berlin", "geometry": 'svg'}, {}, ) assert result_geocode.raw['svg'].startswith('M 13.') async def test_geocode_geometry_kml(self): result_geocode = await self.geocode_run( {"query": "Halensee,Berlin", "geometry": 'kml'}, {}, ) assert result_geocode.raw['geokml'].startswith('') async def test_geocode_geometry_geojson(self): result_geocode = await self.geocode_run( {"query": "Halensee,Berlin", "geometry": 'geojson'}, {}, ) assert result_geocode.raw['geojson'].get('type') == 'Polygon' async def test_missing_reverse_details(self): query = (46.46131, 6.84311) res = await self.reverse_run( {"query": query}, {} ) assert "address" in res.raw res = await self.reverse_run( {"query": query, "addressdetails": False}, {}, ) assert 'address' not in res.raw async def test_viewbox(self): res = await self.geocode_run( {"query": "Maple Street"}, {}, ) assert not (50 <= res.latitude <= 52) assert not (-0.15 <= res.longitude <= -0.11) for viewbox in [ ((52, -0.11), (50, -0.15)), [Point(52, -0.11), Point(50, -0.15)], (("52", "-0.11"), ("50", "-0.15")) ]: await self.geocode_run( {"query": "Maple Street", "viewbox": viewbox}, {"latitude": 51.5223513, "longitude": -0.1382104} ) async def test_bounded(self): bb = (Point('56.588456', '84.719353'), Point('56.437293', '85.296822')) query = ( '\u0441\u0442\u0440\u043e\u0438\u0442\u0435\u043b\u044c ' '\u0442\u043e\u043c\u0441\u043a' ) await self.geocode_run( {"query": query, "viewbox": bb}, {"latitude": 56.4129459, "longitude": 84.847831069814}, ) await self.geocode_run( {"query": query, "viewbox": bb, "bounded": True}, {"latitude": 56.4803224, "longitude": 85.0060457653324}, ) async def test_extratags(self): query = "Statue of Liberty" location = await self.geocode_run( {"query": query}, {}, ) assert location.raw.get('extratags') is None location = await self.geocode_run( {"query": query, "extratags": True}, {}, ) # 'wikidata': 'Q9202', 'wikipedia': 'en:Statue of Liberty' assert location.raw['extratags']['wikidata'] == 'Q9202' async def test_country_codes_moscow(self): await self.geocode_run( {"query": "moscow", "country_codes": "RU"}, {"latitude": 55.7507178, "longitude": 37.6176606, "delta": 0.3}, ) location = await self.geocode_run( {"query": "moscow", "country_codes": "US"}, # There are two possible results: # Moscow Idaho: 46.7323875,-117.0001651 # Moscow Penn: 41.3367497,-75.5185191 {}, ) # We don't care which Moscow is returned, unless it's # the Russian one. We can sort this out by asserting # the longitudes. The Russian Moscow has positive longitudes. assert -119 < location.longitude assert location.longitude < -70 async def test_country_codes_str(self): await self.geocode_run( {"query": "kazan", "country_codes": 'tr'}, {"latitude": 40.2317, "longitude": 32.6839, "delta": 2}, ) async def test_country_codes_list(self): await self.geocode_run( {"query": "kazan", "country_codes": ['cn', 'tr']}, {"latitude": 40.2317, "longitude": 32.6839, "delta": 2}, ) @pytest.mark.parametrize( "payload, expected", [ pytest.param( {"query": "mexico", "featuretype": 'country'}, {"latitude": 22.5000485, "longitude": -100.0000375, "delta": 5.0}, id="country", ), pytest.param( {"query": "mexico", "featuretype": 'state', "country_codes": "US"}, {"latitude": 34.5708167, "longitude": -105.993007, "delta": 2.0}, id="state", ), pytest.param( {"query": "mexico", "featuretype": 'city'}, {"latitude": 19.4326009, "longitude": -99.1333416, "delta": 2.0}, id="city", marks=pytest.mark.xfail(reason='nominatim responds incorrectly here'), ), pytest.param( {"query": "georgia", "featuretype": 'settlement'}, {"latitude": 32.3293809, "longitude": -83.1137366, "delta": 2.0}, id="settlement", ), ] ) async def test_featuretype_param(self, payload, expected): await self.geocode_run(payload, expected) async def test_namedetails(self): query = "Kyoto, Japan" result = await self.geocode_run( {"query": query, "namedetails": True}, {}, ) assert 'namedetails' in result.raw result = await self.geocode_run( {"query": query, "namedetails": False}, {}, ) assert 'namedetails' not in result.raw async def test_reverse_zoom_parameter(self): query = "40.689253199999996, -74.04454817144321" result_reverse = await self.reverse_run( {"query": query, "zoom": 10}, {}, ) assert "New York" in result_reverse.address assert "Statue of Liberty" not in result_reverse.address result_reverse = await self.reverse_run( {"query": query}, {}, ) assert "New York" in result_reverse.address assert "Statue of Liberty" in result_reverse.address class TestNominatim(BaseTestNominatim): @classmethod def make_geocoder(cls, **kwargs): kwargs.setdefault('user_agent', 'geopy-test') return Nominatim(**kwargs) async def test_default_user_agent_error(self): with pytest.raises(ConfigurationError): Nominatim() async def test_example_user_agent_error(self): with pytest.raises(ConfigurationError): Nominatim(user_agent="specify_your_app_name_here") async def test_custom_user_agent_works(self): Nominatim(user_agent='my_application') with patch.object(geopy.geocoders.options, 'default_user_agent', 'my_application'): Nominatim() def test_import_deprecated_osm_module(self): with warnings.catch_warnings(record=True) as w: from geopy.geocoders.osm import Nominatim as OsmNominatim assert len(w) == 1 assert OsmNominatim is Nominatim geopy-2.2.0/test/geocoders/opencage.py0000644000076500000240000000753714072443504020376 0ustar kostyastaff00000000000000import pytest from geopy.exc import ( GeocoderInsufficientPrivileges, GeocoderQuotaExceeded, GeocoderRateLimited, ) from geopy.geocoders import OpenCage from test.geocoders.util import BaseTestGeocoder, env class TestUnitOpenCage: def test_user_agent_custom(self): geocoder = OpenCage( api_key='DUMMYKEY1234', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestOpenCage(BaseTestGeocoder): testing_tokens = { # https://opencagedata.com/api#testingkeys 402: "4372eff77b8343cebfc843eb4da4ddc4", 403: "2e10e5e828262eb243ec0b54681d699a", 429: "d6d0f0065f4348a4bdfe4587ba02714b", } @classmethod def make_geocoder(cls, **kwargs): return OpenCage( api_key=env['OPENCAGE_KEY'], timeout=10, **kwargs ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_geocode_empty_result(self): await self.geocode_run( {"query": "xqj37"}, {}, expect_failure=True ) async def test_bounds(self): await self.geocode_run( {"query": "moscow", # Idaho USA "bounds": [[50.1, -130.1], [44.1, -100.9]]}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_country_str(self): await self.geocode_run( {"query": "kazan", "country": 'tr'}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_country_list(self): await self.geocode_run( {"query": "kazan", "country": ['cn', 'tr']}, {"latitude": 40.2317, "longitude": 32.6839}, ) async def test_geocode_annotations(self): location = await self.geocode_run( {"query": "london"}, {"latitude": 51.5073219, "longitude": -0.1276474}, ) assert location.raw['annotations'] location = await self.geocode_run( {"query": "london", "annotations": False}, {"latitude": 51.5073219, "longitude": -0.1276474}, ) assert 'annotations' not in location.raw async def test_payment_required_error(self, disable_adapter_retries): async with self.inject_geocoder(OpenCage(api_key=self.testing_tokens[402])): with pytest.raises(GeocoderQuotaExceeded) as cm: await self.geocode_run( {"query": "london"}, {}, skiptest_on_errors=False ) assert cm.type is GeocoderQuotaExceeded # urllib: HTTP Error 402: Payment Required # others: Non-successful status code 402 async def test_api_key_disabled_error(self, disable_adapter_retries): async with self.inject_geocoder(OpenCage(api_key=self.testing_tokens[403])): with pytest.raises(GeocoderInsufficientPrivileges) as cm: await self.geocode_run( {"query": "london"}, {}, skiptest_on_errors=False ) assert cm.type is GeocoderInsufficientPrivileges # urllib: HTTP Error 403: Forbidden # others: Non-successful status code 403 async def test_rate_limited_error(self, disable_adapter_retries): async with self.inject_geocoder(OpenCage(api_key=self.testing_tokens[429])): with pytest.raises(GeocoderRateLimited) as cm: await self.geocode_run( {"query": "london"}, {}, skiptest_on_errors=False ) assert cm.type is GeocoderRateLimited # urllib: HTTP Error 429: Too Many Requests # others: Non-successful status code 429 geopy-2.2.0/test/geocoders/openmapquest.py0000644000076500000240000000113614032167233021321 0ustar kostyastaff00000000000000from geopy.geocoders import OpenMapQuest from test.geocoders.nominatim import BaseTestNominatim from test.geocoders.util import env class TestUnitOpenMapQuest: def test_user_agent_custom(self): geocoder = OpenMapQuest( api_key='DUMMYKEY1234', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' class TestOpenMapQuest(BaseTestNominatim): @classmethod def make_geocoder(cls, **kwargs): return OpenMapQuest(api_key=env['OPENMAPQUEST_APIKEY'], timeout=3, **kwargs) geopy-2.2.0/test/geocoders/pelias.py0000644000076500000240000000443714032167233020064 0ustar kostyastaff00000000000000from geopy.geocoders import Pelias from geopy.point import Point from test.geocoders.util import BaseTestGeocoder, env class BaseTestPelias(BaseTestGeocoder): delta = 0.04 known_state_de = "Verwaltungsregion Ionische Inseln" known_state_en = "Ionian Islands Periphery" async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "san josé california"}, {"latitude": 37.33939, "longitude": -121.89496}, ) async def test_reverse(self): await self.reverse_run( {"query": Point(40.75376406311989, -73.98489005863667)}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667} ) async def test_boundary_rect(self): await self.geocode_run( {"query": "moscow", # Idaho USA "boundary_rect": [[50.1, -130.1], [44.1, -100.9]]}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_geocode_language_parameter(self): query = "Graben 7, Wien" result_geocode = await self.geocode_run( {"query": query, "language": "de"}, {} ) assert result_geocode.raw['properties']['country'] == "Österreich" result_geocode = await self.geocode_run( {"query": query, "language": "en"}, {} ) assert result_geocode.raw['properties']['country'] == "Austria" async def test_reverse_language_parameter(self): query = "48.198674, 16.348388" result_reverse_de = await self.reverse_run( {"query": query, "language": "de"}, {}, ) assert result_reverse_de.raw['properties']['country'] == "Österreich" result_reverse_en = await self.reverse_run( {"query": query, "language": "en"}, {}, ) assert result_reverse_en.raw['properties']['country'] == "Austria" class TestPelias(BaseTestPelias): @classmethod def make_geocoder(cls, **kwargs): return Pelias( env['PELIAS_DOMAIN'], api_key=env['PELIAS_KEY'], **kwargs, ) geopy-2.2.0/test/geocoders/photon.py0000644000076500000240000000551214032167233020111 0ustar kostyastaff00000000000000from geopy.geocoders import Photon from geopy.point import Point from test.geocoders.util import BaseTestGeocoder class TestPhoton(BaseTestGeocoder): known_country_de = "Frankreich" known_country_fr = "France" @classmethod def make_geocoder(cls, **kwargs): return Photon(**kwargs) async def test_user_agent_custom(self): geocoder = Photon( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_geocode(self): location = await self.geocode_run( {"query": "14 rue pelisson villeurbanne"}, {"latitude": 45.7733963, "longitude": 4.88612369}, ) assert "France" in location.address async def test_osm_tag(self): await self.geocode_run( {"query": "Freedom", "osm_tag": "tourism:artwork"}, {"latitude": 38.8898061, "longitude": -77.009088, "delta": 2.0}, ) await self.geocode_run( {"query": "Freedom", "osm_tag": ["!office", "place:hamlet"]}, {"latitude": 44.3862491, "longitude": -88.290994, "delta": 2.0}, ) async def test_bbox(self): await self.geocode_run( {"query": "moscow"}, {"latitude": 55.7504461, "longitude": 37.6174943}, ) await self.geocode_run( {"query": "moscow", # Idaho USA "bbox": [[50.1, -130.1], [44.1, -100.9]]}, {"latitude": 46.7323875, "longitude": -117.0001651}, ) async def test_unicode_name(self): await self.geocode_run( {"query": "\u6545\u5bab"}, {"latitude": 39.916, "longitude": 116.390}, ) async def test_reverse(self): result = await self.reverse_run( {"query": Point(45.7733105, 4.8869339)}, {"latitude": 45.7733105, "longitude": 4.8869339} ) assert "France" in result.address async def test_geocode_language_parameter(self): result_geocode = await self.geocode_run( {"query": self.known_country_fr, "language": "de"}, {}, ) assert ( result_geocode.raw['properties']['country'] == self.known_country_de ) async def test_reverse_language_parameter(self): result_reverse_it = await self.reverse_run( {"query": "45.7733105, 4.8869339", "language": "de"}, {}, ) assert ( result_reverse_it.raw['properties']['country'] == self.known_country_de ) result_reverse_fr = await self.reverse_run( {"query": "45.7733105, 4.8869339", "language": "fr"}, {}, ) assert ( result_reverse_fr.raw['properties']['country'] == self.known_country_fr ) geopy-2.2.0/test/geocoders/pickpoint.py0000644000076500000240000000111314032167233020573 0ustar kostyastaff00000000000000import warnings from geopy.geocoders import PickPoint from test.geocoders.nominatim import BaseTestNominatim from test.geocoders.util import env class TestPickPoint(BaseTestNominatim): @classmethod def make_geocoder(cls, **kwargs): return PickPoint(api_key=env['PICKPOINT_KEY'], timeout=3, **kwargs) async def test_no_nominatim_user_agent_warning(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') PickPoint(api_key=env['PICKPOINT_KEY']) assert 0 == len(w) geopy-2.2.0/test/geocoders/smartystreets.py0000644000076500000240000000227414032167233021535 0ustar kostyastaff00000000000000from unittest.mock import patch import geopy.geocoders from geopy.geocoders import LiveAddress from test.geocoders.util import BaseTestGeocoder, env class TestUnitLiveAddress: dummy_id = 'DUMMY12345' dummy_token = 'DUMMY67890' def test_user_agent_custom(self): geocoder = LiveAddress( auth_id=self.dummy_id, auth_token=self.dummy_token, user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' @patch.object(geopy.geocoders.options, 'default_scheme', 'http') def test_default_scheme_is_ignored(self): geocoder = LiveAddress(auth_id=self.dummy_id, auth_token=self.dummy_token) assert geocoder.scheme == 'https' class TestLiveAddress(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return LiveAddress( auth_id=env['LIVESTREETS_AUTH_ID'], auth_token=env['LIVESTREETS_AUTH_TOKEN'], **kwargs ) async def test_geocode(self): await self.geocode_run( {"query": "435 north michigan ave, chicago il 60611 usa"}, {"latitude": 41.890, "longitude": -87.624}, ) geopy-2.2.0/test/geocoders/tomtom.py0000644000076500000240000000330414032167233020116 0ustar kostyastaff00000000000000from geopy.geocoders import TomTom from test.geocoders.util import BaseTestGeocoder, env class BaseTestTomTom(BaseTestGeocoder): async def test_user_agent_custom(self): geocoder = self.make_geocoder( user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_geocode(self): location = await self.geocode_run( {'query': 'москва'}, {'latitude': 55.75587, 'longitude': 37.61768}, ) assert 'Москва' in location.address async def test_reverse(self): location = await self.reverse_run( {'query': '51.5285057, -0.1369635', 'language': 'en-US'}, {'latitude': 51.5285057, 'longitude': -0.1369635, "delta": 0.3}, ) assert 'London' in location.address # Russian Moscow address can be reported differently, so # we're querying something more ordinary, like London. # # For example, AzureMaps might return # `Красная площадь, 109012 Moskva` instead of the expected # `Красная площадь, 109012 Москва`, even when language is # specified explicitly as `ru-RU`. And TomTom always returns # the cyrillic variant, even when the `en-US` language is # requested. async def test_geocode_empty(self): await self.geocode_run( {'query': 'sldkfhdskjfhsdkhgflaskjgf'}, {}, expect_failure=True, ) class TestTomTom(BaseTestTomTom): @classmethod def make_geocoder(cls, **kwargs): return TomTom(env['TOMTOM_KEY'], timeout=3, **kwargs) geopy-2.2.0/test/geocoders/util.py0000644000076500000240000001577214032167233017570 0ustar kostyastaff00000000000000import json import os from abc import ABC, abstractmethod from unittest.mock import ANY, patch import pytest from async_generator import async_generator, asynccontextmanager, yield_ from geopy import exc from geopy.adapters import BaseAsyncAdapter from geopy.location import Location _env = {} try: with open(".test_keys") as fp: _env.update(json.loads(fp.read())) except IOError: _env.update(os.environ) class SkipIfMissingEnv(dict): def __init__(self, env): super().__init__(env) self.is_internet_access_allowed = None def __getitem__(self, key): assert self.is_internet_access_allowed is not None if key not in self: if self.is_internet_access_allowed: pytest.skip("Missing geocoder credential: %s" % (key,)) else: # Generate some dummy token. We won't perform a networking # request anyways. return "dummy" return super().__getitem__(key) env = SkipIfMissingEnv(_env) class BaseTestGeocoder(ABC): """ Base for geocoder-specific test cases. """ geocoder = None delta = 0.5 @pytest.fixture(scope='class', autouse=True) @async_generator async def class_geocoder(_, request, patch_adapter, is_internet_access_allowed): """Prepare a class-level Geocoder instance.""" cls = request.cls env.is_internet_access_allowed = is_internet_access_allowed geocoder = cls.make_geocoder() cls.geocoder = geocoder run_async = isinstance(geocoder.adapter, BaseAsyncAdapter) if run_async: async with geocoder: await yield_(geocoder) else: await yield_(geocoder) @classmethod @asynccontextmanager @async_generator async def inject_geocoder(cls, geocoder): """An async context manager allowing to inject a custom geocoder instance in a single test method which will be used by the `geocode_run`/`reverse_run` methods. """ with patch.object(cls, 'geocoder', geocoder): run_async = isinstance(geocoder.adapter, BaseAsyncAdapter) if run_async: async with geocoder: await yield_(geocoder) else: await yield_(geocoder) @pytest.fixture(autouse=True) def ensure_no_geocoder_assignment(self): yield assert self.geocoder is type(self).geocoder, ( "Detected `self.geocoder` assignment. " "Please use `async with inject_geocoder(my_geocoder):` " "instead, which supports async adapters." ) @classmethod @abstractmethod def make_geocoder(cls, **kwargs): # pragma: no cover pass async def geocode_run( self, payload, expected, *, skiptest_on_errors=True, expect_failure=False, skiptest_on_failure=False ): """ Calls geocoder.geocode(**payload), then checks against `expected`. """ cls = type(self) result = await self._make_request( self.geocoder, 'geocode', skiptest_on_errors=skiptest_on_errors, **payload, ) if expect_failure: assert result is None return if result is None: if skiptest_on_failure: pytest.skip('%s: Skipping test due to empty result' % cls.__name__) else: pytest.fail('%s: No result found' % cls.__name__) if result == []: pytest.fail('%s returned an empty list instead of None' % cls.__name__) self._verify_request(result, exactly_one=payload.get('exactly_one', True), **expected) return result async def reverse_run( self, payload, expected, *, skiptest_on_errors=True, expect_failure=False, skiptest_on_failure=False ): """ Calls geocoder.reverse(**payload), then checks against `expected`. """ cls = type(self) result = await self._make_request( self.geocoder, 'reverse', skiptest_on_errors=skiptest_on_errors, **payload, ) if expect_failure: assert result is None return if result is None: if skiptest_on_failure: pytest.skip('%s: Skipping test due to empty result' % cls.__name__) else: pytest.fail('%s: No result found' % cls.__name__) if result == []: pytest.fail('%s returned an empty list instead of None' % cls.__name__) self._verify_request(result, exactly_one=payload.get('exactly_one', True), **expected) return result async def reverse_timezone_run(self, payload, expected, *, skiptest_on_errors=True): timezone = await self._make_request( self.geocoder, 'reverse_timezone', skiptest_on_errors=skiptest_on_errors, **payload, ) if expected is None: assert timezone is None else: assert timezone.pytz_timezone == expected return timezone async def _make_request(self, geocoder, method, *, skiptest_on_errors, **kwargs): cls = type(self) call = getattr(geocoder, method) run_async = isinstance(geocoder.adapter, BaseAsyncAdapter) try: if run_async: result = await call(**kwargs) else: result = call(**kwargs) except exc.GeocoderRateLimited as e: if not skiptest_on_errors: raise pytest.skip( "%s: Rate-limited, retry-after %s" % (cls.__name__, e.retry_after) ) except exc.GeocoderQuotaExceeded: if not skiptest_on_errors: raise pytest.skip("%s: Quota exceeded" % cls.__name__) except exc.GeocoderTimedOut: if not skiptest_on_errors: raise pytest.skip("%s: Service timed out" % cls.__name__) except exc.GeocoderUnavailable: if not skiptest_on_errors: raise pytest.skip("%s: Service unavailable" % cls.__name__) return result def _verify_request( self, result, latitude=ANY, longitude=ANY, address=ANY, exactly_one=True, delta=None, ): if exactly_one: assert isinstance(result, Location) else: assert isinstance(result, list) item = result if exactly_one else result[0] delta = delta or self.delta expected = ( pytest.approx(latitude, abs=delta) if latitude is not ANY else ANY, pytest.approx(longitude, abs=delta) if longitude is not ANY else ANY, address, ) received = ( item.latitude, item.longitude, item.address, ) assert received == expected geopy-2.2.0/test/geocoders/what3words.py0000644000076500000240000000653414032167233020714 0ustar kostyastaff00000000000000from unittest.mock import patch import pytest import geopy.exc import geopy.geocoders from geopy.geocoders import What3Words, What3WordsV3 from geopy.geocoders.what3words import _check_query from test.geocoders.util import BaseTestGeocoder, env class TestUnitWhat3Words: dummy_api_key = 'DUMMYKEY1234' async def test_user_agent_custom(self): geocoder = What3Words( api_key=self.dummy_api_key, user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' @patch.object(geopy.geocoders.options, 'default_scheme', 'http') def test_default_scheme_is_ignored(self): geocoder = What3Words(api_key=self.dummy_api_key) assert geocoder.scheme == 'https' def test_check_query(self): result_check_threeword_query = _check_query( "\u0066\u0061\u0068\u0072\u0070\u0072" "\u0065\u0069\u0073\u002e\u006c\u00fc" "\u0067\u006e\u0065\u0072\u002e\u006b" "\u0075\u0074\u0073\u0063\u0068\u0065" ) assert result_check_threeword_query class BaseTestWhat3Words(BaseTestGeocoder): async def test_geocode(self): await self.geocode_run( {"query": "piped.gains.jangle"}, {"latitude": 53.037611, "longitude": 11.565012}, ) async def test_reverse(self): await self.reverse_run( {"query": "53.037611,11.565012", "lang": 'DE'}, {"address": 'fortschrittliche.voll.schnitt'}, ) async def test_unicode_query(self): await self.geocode_run( { "query": ( "\u0070\u0069\u0070\u0065\u0064\u002e\u0067" "\u0061\u0069\u006e\u0073\u002e\u006a\u0061" "\u006e\u0067\u006c\u0065" ) }, {"latitude": 53.037611, "longitude": 11.565012}, ) async def test_empty_response(self): with pytest.raises(geopy.exc.GeocoderQueryError): await self.geocode_run( {"query": "definitely.not.existingiswearrrr"}, {}, expect_failure=True ) async def test_not_exactly_one(self): await self.geocode_run( {"query": "piped.gains.jangle", "exactly_one": False}, {"latitude": 53.037611, "longitude": 11.565012}, ) await self.reverse_run( {"query": (53.037611, 11.565012), "exactly_one": False}, {"address": "piped.gains.jangle"}, ) async def test_reverse_language(self): await self.reverse_run( {"query": (53.037611, 11.565012), "lang": "en", "exactly_one": False}, {"address": "piped.gains.jangle"}, ) class TestWhat3Words(BaseTestWhat3Words): @classmethod def make_geocoder(cls, **kwargs): return What3Words( env['WHAT3WORDS_KEY'], timeout=3, **kwargs ) async def test_geocode_language(self): await self.geocode_run( {"query": "piped.gains.jangle", "lang": 'DE'}, {"address": 'fortschrittliche.voll.schnitt'}, ) class TestWhat3WordsV3(BaseTestWhat3Words): @classmethod def make_geocoder(cls, **kwargs): return What3WordsV3( env['WHAT3WORDS_KEY'], timeout=3, **kwargs ) geopy-2.2.0/test/geocoders/yandex.py0000644000076500000240000000377714032167233020105 0ustar kostyastaff00000000000000import pytest from geopy.exc import GeocoderInsufficientPrivileges from geopy.geocoders import Yandex from test.geocoders.util import BaseTestGeocoder, env class TestYandex(BaseTestGeocoder): @classmethod def make_geocoder(cls, **kwargs): return Yandex(api_key=env['YANDEX_KEY'], **kwargs) async def test_user_agent_custom(self): geocoder = Yandex( api_key='mock', user_agent='my_user_agent/1.0' ) assert geocoder.headers['User-Agent'] == 'my_user_agent/1.0' async def test_unicode_name(self): await self.geocode_run( {"query": "площадь Ленина Донецк"}, {"latitude": 48.002104, "longitude": 37.805186}, ) async def test_failure_with_invalid_api_key(self): async with self.inject_geocoder(Yandex(api_key='bad key')): with pytest.raises(GeocoderInsufficientPrivileges): await self.geocode_run( {"query": "площадь Ленина Донецк"}, {} ) async def test_reverse(self): await self.reverse_run( {"query": "40.75376406311989, -73.98489005863667"}, {"latitude": 40.75376406311989, "longitude": -73.98489005863667}, ) async def test_geocode_lang(self): await self.geocode_run( {"query": "площа Леніна Донецьк", "lang": "uk_UA"}, {"address": "площа Леніна, Донецьк, Україна", "latitude": 48.002104, "longitude": 37.805186}, ) async def test_reverse_kind(self): await self.reverse_run( {"query": (55.743659, 37.408055), "kind": "locality"}, {"address": "Москва, Россия"} ) async def test_reverse_lang(self): await self.reverse_run( {"query": (55.743659, 37.408055), "kind": "locality", "lang": "uk_UA"}, {"address": "Москва, Росія"} ) geopy-2.2.0/test/proxy_server.py0000644000076500000240000002253614032167233017404 0ustar kostyastaff00000000000000import base64 import http.server as SimpleHTTPServer import select import socket import socketserver as SocketServer import threading from urllib.request import urlopen def pipe_sockets(sock1, sock2, timeout): """Pipe data from one socket to another and vice-versa.""" sockets = [sock1, sock2] try: while True: rlist, _, xlist = select.select(sockets, [], sockets, timeout) if xlist: break for sock in rlist: data = sock.recv(8096) if not data: # disconnected break other = next(s for s in sockets if s is not sock) other.sendall(data) except IOError: pass finally: for sock in sockets: sock.close() class Future: # concurrent.futures.Future docs say that they shouldn't be instantiated # directly, so this is a simple implementation which mimics the Future # which can safely be instantiated! def __init__(self): self._event = threading.Event() self._result = None self._exc = None def result(self, timeout=None): if not self._event.wait(timeout): raise AssertionError("Future timed out") if self._exc is not None: raise self._exc return self._result def set_result(self, result): self._result = result self._event.set() def set_exception(self, exception): self._exc = exception self._event.set() class ProxyServerThread(threading.Thread): spinup_timeout = 10 def __init__(self, timeout=None): self.proxy_host = 'localhost' self.proxy_port = None # randomly selected by OS self.timeout = timeout self.proxy_server = None self.socket_created_future = Future() self.requests = [] self.auth = None super().__init__() self.daemon = True def reset(self): self.requests.clear() self.auth = None def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop() self.join() def set_auth(self, username, password): self.auth = "%s:%s" % (username, password) def get_proxy_url(self, with_scheme=True): assert self.socket_created_future.result(self.spinup_timeout) if self.auth: auth = "%s@" % self.auth else: auth = "" if with_scheme: scheme = "http://" else: scheme = "" return "%s%s%s:%s" % (scheme, auth, self.proxy_host, self.proxy_port) def run(self): assert not self.proxy_server, ("This class is not reentrable. " "Please create a new instance.") requests = self.requests proxy_thread = self class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): timeout = self.timeout def check_auth(self): if proxy_thread.auth is not None: auth_header = self.headers.get('Proxy-Authorization') b64_auth = base64.standard_b64encode( proxy_thread.auth.encode() ).decode() expected_auth = "Basic %s" % b64_auth if auth_header != expected_auth: self.send_response(401) self.send_header('Connection', 'close') self.end_headers() self.wfile.write( ( "not authenticated. Expected %r, received %r" % (expected_auth, auth_header) ).encode() ) self.connection.close() return False return True def do_GET(self): if not self.check_auth(): return requests.append(self.path) req = urlopen(self.path, timeout=self.timeout) self.send_response(req.getcode()) content_type = req.info().get('content-type', None) if content_type: self.send_header('Content-Type', content_type) self.send_header('Connection', 'close') self.end_headers() self.copyfile(req, self.wfile) self.connection.close() req.close() def do_CONNECT(self): if not self.check_auth(): return requests.append(self.path) # Make a raw TCP connection to the target server host, port = self.path.split(':') try: addr = host, int(port) other_connection = \ socket.create_connection(addr, timeout=self.timeout) except socket.error: self.send_error(502, 'Bad gateway') return # Respond that a tunnel has been created self.send_response(200) self.send_header('Connection', 'close') self.end_headers() pipe_sockets(self.connection, # it closes sockets other_connection, self.timeout) # ThreadingTCPServer offloads connections to separate threads, so # the serve_forever loop doesn't block until connection is closed # (unlike TCPServer). This allows to shutdown the serve_forever loop # even if there's an open connection. try: self.proxy_server = SocketServer.ThreadingTCPServer( (self.proxy_host, 0), Proxy ) # don't hang if there're some open connections self.proxy_server.daemon_threads = True self.proxy_port = self.proxy_server.server_address[1] except Exception as e: self.socket_created_future.set_exception(e) raise else: self.socket_created_future.set_result(True) self.proxy_server.serve_forever() def stop(self): self.proxy_server.shutdown() # stop serve_forever() self.proxy_server.server_close() class HttpServerThread(threading.Thread): spinup_timeout = 10 def __init__(self, timeout=None): self.server_host = 'localhost' self.server_port = None # randomly selected by OS self.timeout = timeout self.http_server = None self.socket_created_future = Future() super(HttpServerThread, self).__init__() self.daemon = True def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop() self.join() def get_server_url(self): assert self.socket_created_future.result(self.spinup_timeout) return "http://%s:%s" % (self.server_host, self.server_port) def run(self): assert not self.http_server, ("This class is not reentrable. " "Please create a new instance.") class Server(SimpleHTTPServer.SimpleHTTPRequestHandler): timeout = self.timeout def do_GET(self): if self.path == "/": self.send_response(200) self.send_header('Connection', 'close') self.end_headers() self.wfile.write(b"Hello world") elif self.path == "/json": self.send_response(200) self.send_header('Content-type', 'application/json') self.send_header('Connection', 'close') self.end_headers() self.wfile.write(b'{"hello":"world"}') elif self.path == "/json/plain": self.send_response(200) self.send_header('Content-type', 'text/plain;charset=utf-8') self.send_header('Connection', 'close') self.end_headers() self.wfile.write(b'{"hello":"world"}') else: self.send_response(404) self.send_header('Connection', 'close') self.send_header('X-test-header', 'hello') self.end_headers() self.wfile.write(b"Not found") self.connection.close() # ThreadingTCPServer offloads connections to separate threads, so # the serve_forever loop doesn't block until connection is closed # (unlike TCPServer). This allows to shutdown the serve_forever loop # even if there's an open connection. try: self.http_server = SocketServer.ThreadingTCPServer( (self.server_host, 0), Server ) # don't hang if there're some open connections self.http_server.daemon_threads = True self.server_port = self.http_server.server_address[1] except Exception as e: self.socket_created_future.set_exception(e) raise else: self.socket_created_future.set_result(True) self.http_server.serve_forever() def stop(self): self.http_server.shutdown() # stop serve_forever() self.http_server.server_close() geopy-2.2.0/test/selfsigned_ca.pem0000644000076500000240000000200113314632132017532 0ustar kostyastaff00000000000000-----BEGIN CERTIFICATE----- MIICyDCCAbACCQC8JTfjHqqtszANBgkqhkiG9w0BAQsFADAlMQ4wDAYDVQQKDAVn ZW9weTETMBEGA1UECwwKdW5pdCB0ZXN0czAgFw0xODA1MDkwODM5MDFaGA8yMTE4 MDQxNTA4MzkwMVowJTEOMAwGA1UECgwFZ2VvcHkxEzARBgNVBAsMCnVuaXQgdGVz dHMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtSG8S9I3M2C+eFcON fOafU4zCd9lQH4W3nY8L9GvvFBLNUXinWRCs4Oh3qaz2X3zlhOfFOXqFJQl653aN GfPUwWIRVa2q0jmuF7AJStjHx1aymnO28QJPK3sRzA2bIFD/v9iyLn4fGk6RkYnV Kf10uKpBDVuKvfTTPkujrYcqKelS03i0+ciC196LCPZdnWCEwjI/IiFtMfDIm2O6 jOGTyKyMef74xl7sHbSrICwxkPM4e+DGbdkTpSKbWRz0QLoA+ygDuEtp0I+MAMr6 1gaM7tttPdHluLjP7mB/vjn5qghs83qoVO29jap6gC/HK1bPqrerD5tOhArARtJF 9F+XAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAF/Bvn6l9GetXh6XZC+zgM4MC/kk SADd/nvo31TmkJgsBFa28CbGPDTfC/jy3ni3IW2sYfTaV9QDqfs5qQVADYqWVTIT Qz5x05UK5wxyWqlbd9wfGcK75ebYFqEaXXwvtuGKyHGwiTeAyUjSAvuwIdjnOe6H uYmte7LvzCzeuElm7hNTCZjI34sna3byX/Umq4Z2IlU6+IVE4M5kpEo1zyR6a3bw nQQkGgulYc5hqw1mIUtXTvafrwYbdk6GdOYzQh1gN6IRdeWVhdKrjrQj5rBgpN47 4LFJhRyczmapflWX4MsROUJBN/UdsMvmfHMalS/rnFqo6YvQlDqBN0dIkCs= -----END CERTIFICATE----- geopy-2.2.0/test/test_distance.py0000644000076500000240000003645414036567704017505 0ustar kostyastaff00000000000000import math import unittest import warnings from geopy.distance import ( EARTH_RADIUS, Distance, GeodesicDistance, GreatCircleDistance, distance, lonlat, ) from geopy.point import Point EARTH_CIRCUMFERENCE = 2 * math.pi * EARTH_RADIUS NORTH_POLE = Point(90, 0) SOUTH_POLE = Point(-90, 0) FIJI = Point(-16.1333333, 180.0) # Vunikondi, Fiji class CommonDistanceComputationCases: cls = None def test_zero_measure(self): self.cls( (40.753152999999998, -73.982275999999999), (40.753152999999998, -73.982275999999999) ) def test_should_have_length_when_only_given_length(self): distance = 1 self.assertEqual(self.cls(distance).kilometers, distance) def test_should_have_zero_distance_for_coincident_points(self): self.assertEqual(self.cls((0, 0), (0, 0)).kilometers, 0) def test_should_have_nonzero_distance_for_distinct_points(self): self.assertTrue(self.cls((0, 0), (0, 1)).kilometers > 0) def test_max_longitude(self): distance = self.cls(kilometers=1.0) destination = distance.destination(FIJI, 45) self.assertAlmostEqual(destination.longitude, -179.99338, 4) def test_should_compute_distance_for_trip_between_poles(self): distance = self.cls(SOUTH_POLE, NORTH_POLE) expected_distance = EARTH_CIRCUMFERENCE / 2 self.assertAlmostEqual(distance.kilometers, expected_distance, -2) def test_should_compute_destination_for_trip_between_poles(self): distance = self.cls(EARTH_CIRCUMFERENCE / 2) destination = distance.destination(NORTH_POLE, 0) self.assertAlmostEqual(destination.latitude, -90, 0) self.assertAlmostEqual(destination.longitude, 0) def test_destination_bearing_east(self): distance = self.cls(kilometers=100) for EAST in (90, 90 + 360, -360+90): p = distance.destination(Point(0, 160), bearing=EAST) self.assertAlmostEqual(p.latitude, 0) self.assertAlmostEqual(p.longitude, 160.8993, delta=1e-3) p = distance.destination(Point(60, 160), bearing=EAST) self.assertAlmostEqual(p.latitude, 59.9878, delta=1e-3) self.assertAlmostEqual(p.longitude, 161.79, delta=1e-2) def test_should_recognize_equivalence_of_pos_and_neg_180_longitude(self): distance1 = self.cls((0, -180), (0, 180)).kilometers distance2 = self.cls((0, 180), (0, -180)).kilometers self.assertAlmostEqual(distance1, 0) self.assertAlmostEqual(distance2, 0) def test_should_compute_distance_across_antimeridian(self): nines = 1 - 1e-30 # 0.(9) distance = self.cls((0, -179 - nines), (0, 179 + nines)).kilometers self.assertAlmostEqual(distance, 0) def test_should_compute_destination_across_antimeridian(self): nines = 1 - 1e-30 # 0.(9) distance = self.cls(10) point = distance.destination((0, -179 - nines), -90) self.assertAlmostEqual(point.latitude, 0.0) self.assertAlmostEqual(point.longitude, 179.91, 3) def test_should_not_tolerate_nans(self): nan = float('nan') with self.assertRaises(ValueError): self.cls((nan, nan), (1, 1)).kilometers with self.assertRaises(ValueError): self.cls((nan, 1), (1, nan)).kilometers with self.assertRaises(ValueError): self.cls((nan, 1), (nan, 1)).kilometers def test_should_compute_distance_for_multiple_points_pairwise(self): dist_total = self.cls((10, 20), (40, 60), (0, 80), (0, 10)) dist1 = self.cls((10, 20), (40, 60)) dist2 = self.cls((40, 60), (0, 80)) dist3 = self.cls((0, 80), (0, 10)) self.assertAlmostEqual(dist_total.kilometers, dist1.km + dist2.km + dist3.km) def test_should_raise_when_using_single_numbers_as_points(self): # Each argument is expected to be a Point. If it's not a point, # it will be wrapped in Point. # Point(10) equals to Point(10, 0). with self.assertRaises(ValueError): self.cls(10, 20) # no error: explicit tuples are not that suspicious self.cls((10, 0), (20, 0)) def test_should_tolerate_tuples_with_textual_numbers(self): dist1 = self.cls(("1", "30"), ("20", "60")) dist2 = self.cls((1, 30), (20, 60)) self.assertAlmostEqual(dist1.kilometers, dist2.kilometers) def test_should_warn_for_mixed_up_lat_lon(self): lat = 40 lon = 120 # should exceed max lat (abs 90) to cause a warning with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') # The correct order is (lat, lon). with self.assertRaises(ValueError): self.cls((lon, lat), (lon - 10, lat)) self.assertEqual(1, len(w)) def test_should_get_consistent_results_for_distance_calculations(self): distance1, distance2 = [self.cls((0, 0), (0, 1)) for _ in range(2)] self.assertEqual(distance1.kilometers, distance2.kilometers) class CommonMathematicalOperatorCases: cls = None def test_should_be_able_to_add_distances(self): added = self.cls(1.0) + self.cls(1.0) self.assertAlmostEqual(added.kilometers, 2.0) def test_should_not_allow_adding_with_objects_that_arent_distances(self): with self.assertRaises(TypeError): self.cls(1.0) + 5 def test_should_be_able_to_negate_distances(self): distance = self.cls(1.0) self.assertAlmostEqual(-(distance.kilometers), (-distance).kilometers) def test_should_be_able_to_subtract_distances(self): subtracted = self.cls(2.0) - self.cls(1.0) self.assertAlmostEqual(subtracted.kilometers, 1) def test_should_be_able_to_multiply_distances_by_floats(self): self.assertAlmostEqual((self.cls(2.0) * 2.0).kilometers, 4.0) def test_should_not_be_able_to_multiply_distances_by_distances(self): with self.assertRaises(TypeError): self.cls(1.0) * self.cls(2.0) def test_should_be_able_to_divide_distances_by_distances(self): ratio = self.cls(4.0) / self.cls(2.0) self.assertIsInstance(ratio, float) self.assertAlmostEqual(ratio, 2.0) def test_should_be_able_to_divide_distances_by_floats(self): divided_distance = self.cls(4.0) / 2.0 self.assertIsInstance(divided_distance, self.cls) self.assertAlmostEqual(divided_distance.kilometers, 2.0) def test_should_be_able_to_take_absolute_value_of_distances(self): self.assertAlmostEqual(abs(self.cls(-1.0)).kilometers, 1.0) def test_should_be_true_in_boolean_context_when_nonzero_length(self): self.assertTrue(self.cls(1.0)) def test_should_be_false_in_boolean_context_when_zero_length(self): self.assertFalse(self.cls(0)) class CommonConversionCases: cls = None def test_should_convert_to_kilometers(self): self.assertEqual(self.cls(1.0).kilometers, 1.0) def test_should_convert_to_kilometers_with_abbreviation(self): self.assertEqual(self.cls(1.0).km, 1.0) def test_should_convert_to_meters(self): self.assertEqual(self.cls(1.0).meters, 1000.0) def test_should_convert_to_meters_with_abbreviation(self): self.assertEqual(self.cls(1.0).m, 1000.0) def test_should_convert_to_miles(self): self.assertAlmostEqual(self.cls(1.0).miles, 0.621371192) def test_should_convert_to_miles_with_abbreviation(self): self.assertAlmostEqual(self.cls(1.0).mi, 0.621371192) def test_should_convert_to_feet(self): self.assertAlmostEqual(self.cls(1.0).feet, 3280.8399, 4) def test_should_convert_to_feet_with_abbreviation(self): self.assertAlmostEqual(self.cls(1.0).ft, 3280.8399, 4) def test_should_convert_to_nautical_miles(self): self.assertAlmostEqual(self.cls(1.0).nautical, 0.539956803) def test_should_convert_to_nautical_miles_with_abbrevation(self): self.assertAlmostEqual(self.cls(1.0).nm, 0.539956803) def test_should_convert_from_meters(self): self.assertEqual(self.cls(meters=1.0).km, 0.001) def test_should_convert_from_feet(self): self.assertAlmostEqual(self.cls(feet=1.0).km, 0.0003048, 8) def test_should_convert_from_miles(self): self.assertAlmostEqual(self.cls(miles=1.0).km, 1.6093440) def test_should_convert_from_nautical_miles(self): self.assertAlmostEqual(self.cls(nautical=1.0).km, 1.8520000) class CommonComparisonCases: cls = None def test_should_support_comparison_with_distance(self): self.assertTrue(self.cls(1) <= self.cls(1)) self.assertTrue(self.cls(1) >= self.cls(1)) self.assertTrue(self.cls(1) < self.cls(2)) self.assertTrue(self.cls(2) > self.cls(1)) self.assertTrue(self.cls(1) == self.cls(1)) self.assertTrue(self.cls(1) != self.cls(2)) self.assertFalse(self.cls(2) <= self.cls(1)) self.assertFalse(self.cls(1) >= self.cls(2)) self.assertFalse(self.cls(2) < self.cls(1)) self.assertFalse(self.cls(1) > self.cls(2)) self.assertFalse(self.cls(1) == self.cls(2)) self.assertFalse(self.cls(1) != self.cls(1)) def test_should_support_comparison_with_number(self): self.assertTrue(1 <= self.cls(1)) self.assertTrue(1 >= self.cls(1)) self.assertTrue(1 < self.cls(2)) self.assertTrue(2 > self.cls(1)) self.assertTrue(1 == self.cls(1)) self.assertTrue(1 != self.cls(2)) self.assertFalse(2 <= self.cls(1)) self.assertFalse(1 >= self.cls(2)) self.assertFalse(2 < self.cls(1)) self.assertFalse(1 > self.cls(2)) self.assertFalse(1 == self.cls(2)) self.assertFalse(1 != self.cls(1)) class CommonDistanceCases(CommonDistanceComputationCases, CommonMathematicalOperatorCases, CommonConversionCases, CommonComparisonCases): pass class TestDefaultDistanceClass(CommonMathematicalOperatorCases, CommonConversionCases, CommonComparisonCases, unittest.TestCase): cls = Distance def test_default_distance(self): self.assertEqual(distance(132).km, 132) def test_lonlat_function(self): newport_ri_xy = (-71.312796, 41.49008) point = lonlat(*newport_ri_xy) self.assertEqual(point, (41.49008, -71.312796, 0)) class TestWhenComputingGreatCircleDistance(CommonDistanceCases, unittest.TestCase): cls = GreatCircleDistance def test_should_compute_distance_for_half_trip_around_equator(self): distance_around_earth = self.cls((0, 0), (0, 180)).kilometers self.assertEqual(distance_around_earth, EARTH_CIRCUMFERENCE / 2) def test_should_compute_destination_for_half_trip_around_equator(self): distance = self.cls(EARTH_CIRCUMFERENCE / 2) destination = distance.destination((0, 0), 0) self.assertAlmostEqual(destination.latitude, 0) self.assertAlmostEqual(destination.longitude, 180) def test_different_altitudes_error(self): with self.assertRaises(ValueError): # Different altitudes raise an exception: self.cls((10, 10, 10), (20, 20, 15)) # Equal non-zero altitudes don't raise: self.cls((10, 10, 10), (20, 20, 10)) class TestWhenComputingGeodesicDistance(CommonDistanceCases, unittest.TestCase): cls = GeodesicDistance def test_different_altitudes_error(self): with self.assertRaises(ValueError): # Different altitudes raise an exception: self.cls((10, 10, 10), (20, 20, 15)) # Equal non-zero altitudes don't raise: self.cls((10, 10, 10), (20, 20, 10)) def test_miscellaneous_high_accuracy_cases(self): testcases = [ [35.60777, -139.44815, 111.098748429560326, -11.17491, -69.95921, 129.289270889708762, 8935244.5604818305], [55.52454, 106.05087, 22.020059880982801, 77.03196, 197.18234, 109.112041110671519, 4105086.1713924406], [-21.97856, 142.59065, -32.44456876433189, 41.84138, 98.56635, -41.84359951440466, 8394328.894657671], [-66.99028, 112.2363, 173.73491240878403, -12.70631, 285.90344, 2.512956620913668, 11150344.2312080241], [-17.42761, 173.34268, -159.033557661192928, -15.84784, 5.93557, -20.787484651536988, 16076603.1631180673], [32.84994, 48.28919, 150.492927788121982, -56.28556, 202.29132, 48.113449399816759, 16727068.9438164461], [6.96833, 52.74123, 92.581585386317712, -7.39675, 206.17291, 90.721692165923907, 17102477.2496958388], [-50.56724, -16.30485, -105.439679907590164, -33.56571, -94.97412, -47.348547835650331, 6455670.5118668696], [-58.93002, -8.90775, 140.965397902500679, -8.91104, 133.13503, 19.255429433416599, 11756066.0219864627], [-68.82867, -74.28391, 93.774347763114881, -50.63005, -8.36685, 34.65564085411343, 3956936.926063544], [-10.62672, -32.0898, -86.426713286747751, 5.883, -134.31681, -80.473780971034875, 11470869.3864563009], [-21.76221, 166.90563, 29.319421206936428, 48.72884, 213.97627, 43.508671946410168, 9098627.3986554915], [-19.79938, -174.47484, 71.167275780171533, -11.99349, -154.35109, 65.589099775199228, 2319004.8601169389], [-11.95887, -116.94513, 92.712619830452549, 4.57352, 7.16501, 78.64960934409585, 13834722.5801401374], [-87.85331, 85.66836, -65.120313040242748, 66.48646, 16.09921, -4.888658719272296, 17286615.3147144645], [1.74708, 128.32011, -101.584843631173858, -11.16617, 11.87109, -86.325793296437476, 12942901.1241347408], [-25.72959, -144.90758, -153.647468693117198, -57.70581, -269.17879, -48.343983158876487, 9413446.7452453107], [-41.22777, 122.32875, 14.285113402275739, -7.57291, 130.37946, 10.805303085187369, 3812686.035106021], [11.01307, 138.25278, 79.43682622782374, 6.62726, 247.05981, 103.708090215522657, 11911190.819018408], [-29.47124, 95.14681, -163.779130441688382, -27.46601, -69.15955, -15.909335945554969, 13487015.8381145492]] d = self.cls(ellipsoid='WGS-84') km = 1000 for tup in testcases: (lat1, lon1, azi1, lat2, lon2, azi2, s12) = tup p1, p2 = Point(lat1, lon1), Point(lat2, lon2) s12a = d.measure(p1, p2) * km self.assertAlmostEqual(s12a, s12, delta=1e-8) p = d.destination(p1, azi1, s12/km) self.assertAlmostEqual(p.latitude, p2.latitude, delta=1e-13) self.assertAlmostEqual(p.longitude, p2.longitude, delta=1e-12) p = d.destination(p2, azi2, -s12/km) self.assertAlmostEqual(p.latitude, p1.latitude, delta=1e-13) self.assertAlmostEqual(p.longitude, p1.longitude, delta=1e-12) geopy-2.2.0/test/test_exc.py0000644000076500000240000000064614034400633016445 0ustar kostyastaff00000000000000import inspect import pytest import geopy.exc error_classes = sorted( [ v for v in (getattr(geopy.exc, name) for name in dir(geopy.exc)) if inspect.isclass(v) and issubclass(v, geopy.exc.GeopyError) ], key=lambda cls: cls.__name__, ) @pytest.mark.parametrize("error_cls", error_classes) def test_init(error_cls): with pytest.raises(error_cls): raise error_cls("dummy") geopy-2.2.0/test/test_format.py0000644000076500000240000000154613673631726017177 0ustar kostyastaff00000000000000import pytest from geopy.format import format_degrees from geopy.point import Point @pytest.mark.parametrize( "input, expected", [ (("12", "30", 0), "12 30' 0\""), (("12", "30", "30"), "12 30' 30\""), (("12", "30", 30.4), "12 30' 30.4\""), ], ) def test_format_simple(input, expected): assert format_degrees(Point.parse_degrees(*input)) == expected # These tests currently fail, because conversion to degrees (as float) # causes loss in precision (mostly because of divisions by 3): @pytest.mark.xfail @pytest.mark.parametrize( "input, expected", [ (("13", "20", 0), "13 20' 0\""), # actual: `13 20' 2.13163e-12"` (("-13", "19", 0), "-13 19' 0\""), # actual: `-13 18' 60"` ], ) def test_format_float_precision(input, expected): assert format_degrees(Point.parse_degrees(*input)) == expected geopy-2.2.0/test/test_init.py0000644000076500000240000000070113673631727016643 0ustar kostyastaff00000000000000from distutils.version import LooseVersion from geopy import __version__, __version_info__, get_version def test_version(): assert isinstance(__version__, str) and __version__ def test_version_info(): expected_version_info = tuple(LooseVersion(__version__).version) assert expected_version_info == __version_info__ def test_get_version(): version = get_version() assert isinstance(version, str) and version == __version__ geopy-2.2.0/test/test_location.py0000644000076500000240000001144613717774172017521 0ustar kostyastaff00000000000000import pickle import unittest from geopy.location import Location from geopy.point import Point GRAND_CENTRAL_STR = "89 E 42nd St New York, NY 10017" GRAND_CENTRAL_COORDS_STR = "40.752662,-73.9773" GRAND_CENTRAL_COORDS_TUPLE = (40.752662, -73.9773, 0) GRAND_CENTRAL_POINT = Point(GRAND_CENTRAL_COORDS_STR) GRAND_CENTRAL_RAW = { 'id': '1', 'class': 'place', 'lat': '40.752662', 'lon': '-73.9773', 'display_name': "89, East 42nd Street, New York, " "New York, 10017, United States of America", } class LocationTestCase(unittest.TestCase): def _location_iter_test( self, loc, ref_address=GRAND_CENTRAL_STR, ref_longitude=GRAND_CENTRAL_COORDS_TUPLE[0], ref_latitude=GRAND_CENTRAL_COORDS_TUPLE[1] ): address, (latitude, longitude) = loc self.assertEqual(address, ref_address) self.assertEqual(latitude, ref_longitude) self.assertEqual(longitude, ref_latitude) def _location_properties_test(self, loc, raw=None): self.assertEqual(loc.address, GRAND_CENTRAL_STR) self.assertEqual(loc.latitude, GRAND_CENTRAL_COORDS_TUPLE[0]) self.assertEqual(loc.longitude, GRAND_CENTRAL_COORDS_TUPLE[1]) self.assertEqual(loc.altitude, GRAND_CENTRAL_COORDS_TUPLE[2]) if raw is not None: self.assertEqual(loc.raw, raw) def test_location_str(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_COORDS_STR, {}) self._location_iter_test(loc) self.assertEqual(loc.point, GRAND_CENTRAL_POINT) def test_location_point(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) self._location_iter_test(loc) self.assertEqual(loc.point, GRAND_CENTRAL_POINT) def test_location_none(self): with self.assertRaises(TypeError): Location(GRAND_CENTRAL_STR, None, {}) def test_location_iter(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_COORDS_TUPLE, {}) self._location_iter_test(loc) self.assertEqual(loc.point, GRAND_CENTRAL_POINT) def test_location_point_typeerror(self): with self.assertRaises(TypeError): Location(GRAND_CENTRAL_STR, 1, {}) def test_location_array_access(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_COORDS_TUPLE, {}) self.assertEqual(loc[0], GRAND_CENTRAL_STR) self.assertEqual(loc[1][0], GRAND_CENTRAL_COORDS_TUPLE[0]) self.assertEqual(loc[1][1], GRAND_CENTRAL_COORDS_TUPLE[1]) def test_location_properties(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) self._location_properties_test(loc) def test_location_raw(self): loc = Location( GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, raw=GRAND_CENTRAL_RAW ) self._location_properties_test(loc, GRAND_CENTRAL_RAW) def test_location_string(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) self.assertEqual(str(loc), loc.address) def test_location_len(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) self.assertEqual(len(loc), 2) def test_location_eq(self): loc1 = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) loc2 = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_COORDS_TUPLE, {}) self.assertEqual(loc1, loc2) def test_location_ne(self): loc1 = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) loc2 = Location(GRAND_CENTRAL_STR, Point(0, 0), {}) self.assertNotEqual(loc1, loc2) def test_location_repr(self): address = ( "22, Ksi\u0119dza Paw\u0142a Po\u015bpiecha, " "Centrum Po\u0142udnie, Zabrze, wojew\xf3dztwo " "\u015bl\u0105skie, 41-800, Polska" ) point = (0.0, 0.0, 0.0) loc = Location(address, point, {}) self.assertEqual( repr(loc), "Location(%s, %r)" % (address, point) ) def test_location_is_picklable(self): loc = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, {}) # https://docs.python.org/2/library/pickle.html#data-stream-format for protocol in (0, 1, 2, -1): pickled = pickle.dumps(loc, protocol=protocol) loc_unp = pickle.loads(pickled) self.assertEqual(loc, loc_unp) def test_location_with_unpicklable_raw(self): some_class = type('some_class', (object,), {}) raw_unpicklable = dict(missing=some_class()) del some_class loc_unpicklable = Location(GRAND_CENTRAL_STR, GRAND_CENTRAL_POINT, raw_unpicklable) for protocol in (0, 1, 2, -1): with self.assertRaises((AttributeError, pickle.PicklingError)): pickle.dumps(loc_unpicklable, protocol=protocol) geopy-2.2.0/test/test_point.py0000644000076500000240000003200114032167233017011 0ustar kostyastaff00000000000000import math import pickle import sys import unittest import warnings from geopy.point import Point class PointTestCase(unittest.TestCase): lat = 40.74113 lon = -73.989656 alt = 3 coords = (lat, lon, alt) def test_point_float(self): point = Point(self.lat, self.lon, self.alt) self.assertEqual(point.longitude, self.lon) self.assertEqual(point.latitude, self.lat) self.assertEqual(point.altitude, self.alt) def test_point_str_simple(self): for each in ("%s,%s", "%s %s", "%s;%s"): point = Point(each % (self.lat, self.lon)) self.assertEqual(point.longitude, self.lon) self.assertEqual(point.latitude, self.lat) self.assertEqual(point.altitude, 0) def test_point_str_deg(self): point = Point("UT: N 39\xb020' 0'' / W 74\xb035' 0''") self.assertEqual(point.latitude, 39.333333333333336) self.assertEqual(point.longitude, -74.58333333333333) self.assertEqual(point.altitude, 0) def test_point_format(self): point = Point("51 19m 12.9s N, 0 1m 24.95s E") self.assertEqual(point.format(), "51 19m 12.9s N, 0 1m 24.95s E") point = Point("51 19m 12.9s N, -1 1m 24.95s E, 15000m") self.assertEqual(point.format(), "51 19m 12.9s N, 1 1m 24.95s W, 15.0km") # TODO # point = Point("51 19m 12.9s N, -0 1m 24.95s E") # self.assertEqual(point.format(), "51 19m 12.9s N, 0 1m 24.95s W") # TODO # with self.assertRaises(ValueError): # # Z is not a valid direction # Point("51 19m 12.9s Z, 0 1m 24.95s E") with self.assertRaises(ValueError): Point("gibberish") with self.assertRaises(ValueError): # It could be interpreted as `Point(75, 5)`. Point("75 5th Avenue, NYC, USA") def test_point_from_string(self): # Examples are from the docstring of `Point.from_string`. self.assertEqual(Point("41.5;-81.0"), (41.5, -81.0, 0.0)) self.assertEqual(Point("41.5,-81.0"), (41.5, -81.0, 0.0)) self.assertEqual(Point("41.5 -81.0"), (41.5, -81.0, 0.0)) self.assertEqual(Point("+41.5 -81.0"), (41.5, -81.0, 0.0)) self.assertEqual(Point("+41.5 +81.0"), (41.5, 81.0, 0.0)) self.assertEqual(Point("41.5 N -81.0 W"), (41.5, 81.0, 0.0)) self.assertEqual(Point("-41.5 S;81.0 E"), (41.5, 81.0, 0.0)) self.assertEqual(Point("23 26m 22s N 23 27m 30s E"), (23.439444444444444, 23.458333333333332, 0.0)) self.assertEqual(Point("23 26' 22\" N 23 27' 30\" E"), (23.439444444444444, 23.458333333333332, 0.0)) self.assertEqual(Point("UT: N 39\xb020' 0'' / W 74\xb035' 0''"), (39.333333333333336, -74.58333333333333, 0.0)) def test_point_format_altitude(self): point = Point(latitude=41.5, longitude=81.0, altitude=2.5) self.assertEqual(point.format(), "41 30m 0s N, 81 0m 0s E, 2.5km") self.assertEqual(point.format_decimal(), "41.5, 81.0, 2.5km") self.assertEqual(point.format_decimal('m'), "41.5, 81.0, 2500.0m") point = Point(latitude=41.5, longitude=81.0) self.assertEqual(point.format_decimal(), "41.5, 81.0") self.assertEqual(point.format_decimal('m'), "41.5, 81.0, 0.0m") def test_point_from_iterable(self): self.assertEqual(Point(1, 2, 3), Point([1, 2, 3])) self.assertEqual(Point(1, 2, 0), Point([1, 2])) with self.assertRaises(ValueError): Point([1]) self.assertEqual(Point(0, 0, 0), Point([])) with self.assertRaises(ValueError): Point([1, 2, 3, 4]) def test_point_from_single_number(self): with self.assertRaises(ValueError): # Point from a single number is probably a misuse, # thus it's discouraged. Point(5) # But an explicit zero longitude is fine self.assertEqual((5, 0, 0), tuple(Point(5, 0))) def test_point_from_point(self): point = Point(self.lat, self.lon, self.alt) point_copy = Point(point) self.assertTrue(point is not point_copy) self.assertEqual(tuple(point), tuple(point_copy)) def test_point_from_generator(self): point = Point(i + 10 for i in range(3)) self.assertEqual((10, 11, 12), tuple(point)) self.assertEqual((10, 11, 12), tuple(point)) def test_point_degrees_are_normalized(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') with self.assertRaises(ValueError): # Latitude normalization is not allowed Point(95, 185, 375) self.assertEqual(1, len(w)) with self.assertRaises(ValueError): # Latitude normalization is not allowed Point(-95, -185, 375) self.assertEqual(2, len(w)) point = Point(-85, 185, 375) self.assertEqual((-85, -175, 375), tuple(point)) point = Point(85, -185, 375) self.assertEqual((85, 175, 375), tuple(point)) # note that the zeros might be negative point = Point(-0.0, -0.0, 375) self.assertEqual((0.0, 0.0, 375.0), tuple(point)) # ensure that negative zeros are normalized to the positive ones self.assertEqual((1.0, 1.0, 1.0), tuple(math.copysign(1.0, x) for x in point)) point = Point(90, 180, 375) self.assertEqual((90, 180, 375), tuple(point)) point = Point(-90, -180, 375) self.assertEqual((-90, -180, 375), tuple(point)) self.assertEqual(2, len(w)) point = Point(-90, -540, 375) self.assertEqual((-90, -180, 375), tuple(point)) point = Point(90, 540, 375) self.assertEqual((90, -180, 375), tuple(point)) self.assertEqual(2, len(w)) def test_point_degrees_normalization_does_not_lose_precision(self): if sys.float_info.mant_dig != 53: # pragma: no cover raise unittest.SkipTest('This platform does not store floats as ' 'IEEE 754 double') # IEEE 754 double is stored like this: # sign (1 bit) | exponent (11 bit) | fraction (52 bit) # \/ \/ # 0100000111010010011001001001001011000010100000000000000000000000 # # The issue is that there might be a loss in precision during # normalization. # For example, 180.00000000000003 is stored like this: # \/ \/ # 0100000001100110100000000000000000000000000000000000000000000001 # # And if we do (180.00000000000003 + 180 - 180), then we would get # exactly 180.0: # 0100000001100110100000000000000000000000000000000000000000000000 # # Notice that the last fraction bit has been lost, because # (180.00000000000003 + 180) fraction doesn't fit in 52 bits. # # This test ensures that such unwanted precision loss is not happening. self.assertEqual(tuple(Point(-89.99999999999998, 180.00000000000003)), (-89.99999999999998, -179.99999999999997, 0)) self.assertEqual(tuple(Point(9.000000000000002, 1.8000000000000003)), (9.000000000000002, 1.8000000000000003, 0)) def test_unpacking(self): point = Point(self.lat, self.lon, self.alt) lat, lon, alt = point self.assertEqual(lat, self.lat) self.assertEqual(lon, self.lon) self.assertEqual(alt, self.alt) def test_point_no_len(self): point = Point(self.lat, self.lon) # is it 2 or 3? with self.assertRaises(TypeError): # point doesn't support len() len(point) def test_point_getitem(self): point = Point(self.lat, self.lon, self.alt) self.assertEqual(point[0], self.lat) self.assertEqual(point[1], self.lon) self.assertEqual(point[2], self.alt) def test_point_slices(self): point = Point(self.lat, self.lon, self.alt) self.assertEqual((self.lat, self.lon), point[:2]) self.assertEqual(self.coords, point[:10]) self.assertEqual(self.coords, point[:]) self.assertEqual(self.coords[::-1], point[::-1]) with self.assertRaises(IndexError): point[10] with self.assertRaises(TypeError): point[None] point[0:2] = (self.lat + 10, self.lon + 10) self.assertEqual((self.lat + 10, self.lon + 10, self.alt), tuple(point)) def test_point_setitem(self): point = Point(self.lat + 10, self.lon + 10, self.alt + 10) for each in (0, 1, 2): point[each] = point[each] - 10 self.assertEqual(point[0], self.lat) self.assertEqual(point[1], self.lon) self.assertEqual(point[2], self.alt) self.assertEqual(self.coords, tuple(point)) self.assertEqual(point.latitude, self.lat) self.assertEqual(point.longitude, self.lon) self.assertEqual(point.altitude, self.alt) def test_point_setitem_normalization(self): point = Point() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') with self.assertRaises(ValueError): point[0] = 100 self.assertEqual(1, len(w)) self.assertEqual((0, 0, 0), tuple(point)) point[0] = -80 point[1] = 200 # Please note that attribute assignments are not normalized. # Only __setitem__ assignments are. self.assertEqual((-80, -160, 0), tuple(point)) with self.assertRaises(ValueError): point[1] = float("nan") self.assertEqual(1, len(w)) self.assertEqual((-80, -160, 0), tuple(point)) def test_point_assign_coordinates(self): point = Point(self.lat + 10, self.lon + 10, self.alt + 10) point.latitude = self.lat point.longitude = self.lon point.altitude = self.alt self.assertEqual(point[0], self.lat) self.assertEqual(point[1], self.lon) self.assertEqual(point[2], self.alt) self.assertEqual(self.coords, tuple(point)) self.assertEqual(point.latitude, self.lat) self.assertEqual(point.longitude, self.lon) self.assertEqual(point.altitude, self.alt) def test_point_eq(self): self.assertEqual( Point(self.lat, self.lon), Point("%s %s" % (self.lat, self.lon)) ) def test_point_ne(self): self.assertTrue( Point(self.lat, self.lon, self.alt) != Point(self.lat+10, self.lon-10, self.alt) ) def test_point_comparison_does_not_raise_exceptions(self): point = Point(self.lat, self.lon, self.alt) number = 1 not_iterable = object() self.assertFalse(point == number) self.assertTrue(point != number) self.assertFalse(point == not_iterable) self.assertTrue(point != not_iterable) def test_point_comparison_with_empty_values(self): empty_values = (None, '', [], (), {}) # Django validators point_nonempty = Point(self.lat, self.lon, self.alt) point_empty = Point() # actually Point(0, 0, 0), which is not "empty" self.assertFalse(point_nonempty in empty_values) self.assertTrue(point_nonempty) # Point() == (0, 0, 0). self.assertEqual(Point(), (0, 0, 0)) # Currently Point can't distinguish between zero and unset coordinate # values, so we cannot tell if `point_empty` is really "empty" # (i.e. unset, like `Point()`) or is just a point at the center # (which is obviously not "empty"). That's why on the next line # we assume that `point_empty` is not in the `empty_values` list. self.assertFalse(point_empty in empty_values) # bool(Point(0, 0, 0)) should also be true self.assertTrue(point_empty) def test_point_comparison_respects_lists(self): point = Point(self.lat, self.lon, self.alt) l_eq = [self.lat, self.lon, self.alt] l_ne = [self.lat + 1, self.lon, self.alt] self.assertTrue(l_eq == point) self.assertTrue(point == l_eq) self.assertFalse(l_eq != point) self.assertFalse(point != l_eq) self.assertFalse(l_ne == point) self.assertFalse(point == l_ne) self.assertTrue(l_ne != point) self.assertTrue(point != l_ne) def test_point_comparison_ignores_strings(self): point = Point("1", "2", "3") self.assertFalse(point == "123") self.assertTrue(point != "123") self.assertTrue(point == (1, 2, 3)) def test_point_is_picklable(self): point = Point(self.lat, self.lon, self.alt) # https://docs.python.org/2/library/pickle.html#data-stream-format for protocol in (0, 1, 2, -1): pickled = pickle.dumps(point, protocol=protocol) point_unp = pickle.loads(pickled) self.assertEqual(point, point_unp) self.assertEqual(self.coords, point_unp) geopy-2.2.0/test/test_timezone.py0000644000076500000240000000576414032167233017532 0ustar kostyastaff00000000000000import datetime import pickle import unittest import pytest from geopy.timezone import Timezone, from_fixed_gmt_offset, from_timezone_name try: import pytz import pytz.tzinfo pytz_available = True except ImportError: pytz_available = False @pytest.mark.skipif("not pytz_available") class TimezoneTestCase(unittest.TestCase): timezone_gmt_offset_hours = 3.0 timezone_name = "Europe/Moscow" # a DST-less timezone def test_create_from_timezone_name(self): raw = dict(foo="bar") tz = from_timezone_name(self.timezone_name, raw) self.assertEqual(tz.raw['foo'], 'bar') self.assertIsInstance(tz.pytz_timezone, pytz.tzinfo.BaseTzInfo) self.assertIsInstance(tz.pytz_timezone, datetime.tzinfo) def test_create_from_fixed_gmt_offset(self): raw = dict(foo="bar") tz = from_fixed_gmt_offset(self.timezone_gmt_offset_hours, raw) self.assertEqual(tz.raw['foo'], 'bar') # pytz.FixedOffset is not an instanse of pytz.tzinfo.BaseTzInfo. self.assertIsInstance(tz.pytz_timezone, datetime.tzinfo) olson_tz = pytz.timezone(self.timezone_name) dt = datetime.datetime.utcnow() self.assertEqual(tz.pytz_timezone.utcoffset(dt), olson_tz.utcoffset(dt)) def test_create_from_pytz_timezone(self): pytz_timezone = pytz.timezone(self.timezone_name) tz = Timezone(pytz_timezone, {}) self.assertIs(tz.pytz_timezone, pytz_timezone) def test_string(self): raw = dict(foo="bar") tz = from_timezone_name(self.timezone_name, raw) self.assertEqual(str(tz), self.timezone_name) def test_repr(self): raw = dict(foo="bar") pytz_timezone = pytz.timezone(self.timezone_name) tz = Timezone(pytz_timezone, raw) self.assertEqual(repr(tz), "Timezone(%s)" % repr(pytz_timezone)) def test_eq(self): tz = pytz.timezone("Europe/Paris") raw1 = dict(a=1) raw2 = dict(a=1) self.assertEqual(Timezone(tz, raw1), Timezone(tz, raw2)) def test_ne(self): tz1 = pytz.timezone("Europe/Paris") tz2 = pytz.timezone("Europe/Prague") raw = {} self.assertNotEqual(Timezone(tz1, raw), Timezone(tz2, raw)) def test_picklable(self): raw = dict(foo="bar") tz = from_timezone_name(self.timezone_name, raw) # https://docs.python.org/2/library/pickle.html#data-stream-format for protocol in (0, 1, 2, -1): pickled = pickle.dumps(tz, protocol=protocol) tz_unp = pickle.loads(pickled) self.assertEqual(tz, tz_unp) def test_with_unpicklable_raw(self): some_class = type('some_class', (object,), {}) raw_unpicklable = dict(missing=some_class()) del some_class tz_unpicklable = from_timezone_name(self.timezone_name, raw_unpicklable) for protocol in (0, 1, 2, -1): with self.assertRaises((AttributeError, pickle.PicklingError)): pickle.dumps(tz_unpicklable, protocol=protocol)