-
Notifications
You must be signed in to change notification settings - Fork 636
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bd4f744
commit ad31b19
Showing
4 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
from geopy.compat 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/ | ||
.. versionadded:: 1.21.0 | ||
""" | ||
|
||
api_path = '/geocoding/%(query)s.json' | ||
|
||
def __init__( | ||
self, | ||
api_key, | ||
format_string=None, | ||
scheme=None, | ||
timeout=DEFAULT_SENTINEL, | ||
proxies=DEFAULT_SENTINEL, | ||
user_agent=None, | ||
ssl_context=DEFAULT_SENTINEL, | ||
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 format_string: | ||
See :attr:`geopy.geocoders.options.default_format_string`. | ||
: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 str domain: base api domain for Maptiler | ||
""" | ||
super(MapTiler, self).__init__( | ||
format_string=format_string, | ||
scheme=scheme, | ||
timeout=timeout, | ||
proxies=proxies, | ||
user_agent=user_agent, | ||
ssl_context=ssl_context, | ||
) | ||
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 str language: Prefer results in specific languages. It's possible | ||
to specify multiple values. e.g. "de,en" | ||
: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 = self.format_string % query | ||
if bbox: | ||
params['bbox'] = self._format_bounding_box( | ||
bbox, "%(lon1)s,%(lat1)s,%(lon2)s,%(lat2)s") | ||
|
||
if language: | ||
params['language'] = 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) | ||
return self._parse_json( | ||
self._call_geocoder(url, timeout=timeout), exactly_one | ||
) | ||
|
||
def reverse( | ||
self, | ||
query, | ||
exactly_one=True, | ||
language=None, | ||
timeout=DEFAULT_SENTINEL, | ||
): | ||
""" | ||
Return an address by location point. | ||
:param str language: Prefer results in specific languages. It's possible | ||
to specify multiple values. e.g. "de,en" | ||
: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 = {'key': self.api_key} | ||
|
||
if language: | ||
params['language'] = 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) | ||
return self._parse_json( | ||
self._call_geocoder(url, timeout=timeout), exactly_one | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# -*- coding: utf-8 -*- | ||
import unittest | ||
|
||
from geopy.compat import u | ||
from geopy.geocoders import MapTiler | ||
from geopy.location import Location | ||
from geopy.point import Point | ||
from test.geocoders.util import GeocoderTestBase, env | ||
|
||
|
||
@unittest.skipUnless( | ||
bool(env.get('MAPTILER_KEY')), | ||
"No MAPTILER_KEY env variable set" | ||
) | ||
class MapTilerTestCase(GeocoderTestBase): | ||
@classmethod | ||
def setUpClass(cls): | ||
cls.geocoder = MapTiler(api_key=env['MAPTILER_KEY'], timeout=3) | ||
|
||
def test_geocode(self): | ||
self.geocode_run( | ||
{"query": "435 north michigan ave, chicago il 60611 usa"}, | ||
{"latitude": 41.890, "longitude": -87.624}, | ||
) | ||
|
||
def test_unicode_name(self): | ||
self.geocode_run( | ||
{"query": u("Stadelhoferstrasse 8, 8001 Z\u00fcrich")}, | ||
{"latitude": 47.36649, "longitude": 8.54855}, | ||
) | ||
|
||
def test_reverse(self): | ||
new_york_point = Point(40.75376406311989, -73.98489005863667) | ||
location = self.reverse_run( | ||
{"query": new_york_point, "exactly_one": True}, | ||
{"latitude": 40.7537640, "longitude": -73.98489, "delta": 1}, | ||
) | ||
self.assertIn("New York", location.address) | ||
|
||
def test_zero_results(self): | ||
self.geocode_run( | ||
{"query": 'asdfasdfasdf'}, | ||
{}, | ||
expect_failure=True, | ||
) | ||
|
||
def test_geocode_outside_bbox(self): | ||
self.geocode_run( | ||
{ | ||
"query": "435 north michigan ave, chicago il 60611 usa", | ||
"bbox": [[34.172684, -118.604794], | ||
[34.236144, -118.500938]] | ||
}, | ||
{}, | ||
expect_failure=True, | ||
) | ||
|
||
def test_geocode_bbox(self): | ||
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}, | ||
) | ||
|
||
def test_geocode_proximity(self): | ||
self.geocode_run( | ||
{"query": "200 queen street", "proximity": Point(45.3, -66.1)}, | ||
{"latitude": 44.038901, "longitude": -64.73052, "delta": 0.1}, | ||
) | ||
|
||
def test_reverse_language(self): | ||
zurich_point = Point(47.3723, 8.5422) | ||
location = self.reverse_run( | ||
{"query": zurich_point, "exactly_one": True, "language": "ja"}, | ||
{"latitude": 47.3723, "longitude": 8.5422, "delta": 1}, | ||
) | ||
self.assertIn("チューリッヒ", location.address) | ||
|
||
def test_geocode_language(self): | ||
location = self.geocode_run( | ||
{"query": "Zürich", "exactly_one": True, "language": "ja", | ||
"proximity": Point(47.3723, 8.5422)}, | ||
{"latitude": 47.3723, "longitude": 8.5422, "delta": 1}, | ||
) | ||
self.assertIn("チューリッヒ", location.address) | ||
|
||
def test_geocode_raw(self): | ||
result = self.geocode_run({"query": "New York"}, {}) | ||
delta = 0.00001 | ||
self.assertTrue(isinstance(result.raw, dict)) | ||
self.assertAlmostEqual(-73.8784155, result.raw['center'][0], delta=delta) | ||
self.assertAlmostEqual(40.6930727, result.raw['center'][1], delta=delta) | ||
self.assertEqual("relation175905", result.raw['properties']['osm_id']) | ||
|
||
def test_geocode_exactly_one_false(self): | ||
list_result = self.geocode_run({"query": "New York", "exactly_one": False}, {}) | ||
self.assertTrue(isinstance(list_result, list)) | ||
|
||
def test_geocode_exactly_one_true(self): | ||
list_result = self.geocode_run({"query": "New York", "exactly_one": True}, {}) | ||
self.assertTrue(isinstance(list_result, Location)) |