Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 84 additions & 2 deletions geopy/geocoders/geonames.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
from geopy.exc import (
ConfigurationError,
GeocoderInsufficientPrivileges,
GeocoderQueryError,
GeocoderServiceError,
)
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__ = ("GeoNames", )
Expand All @@ -25,6 +27,8 @@ class GeoNames(Geocoder):

geocode_path = '/searchJSON'
reverse_path = '/findNearbyPlaceNameJSON'
reverse_nearby_path = '/findNearbyJSON'
timezone_path = '/timezoneJSON'

def __init__(
self,
Expand Down Expand Up @@ -87,6 +91,12 @@ def __init__(
self.api_reverse = (
"%s://%s%s" % (self.scheme, domain, self.reverse_path)
)
self.api_timezone = (
"%s://%s%s" % (self.scheme, domain, self.timezone_path)
)
self.api_reverse_nearby = (
"%s://%s%s" % (self.scheme, domain, self.reverse_nearby_path)
)

def geocode(self, query, exactly_one=True, timeout=DEFAULT_SENTINEL):
"""
Expand Down Expand Up @@ -125,6 +135,9 @@ def reverse(
query,
exactly_one=DEFAULT_SENTINEL,
timeout=DEFAULT_SENTINEL,
feature_code=None,
lang=None,
find_nearby_type='findNearbyPlaceNameJSON',
):
"""
Return an address by location point.
Expand All @@ -150,6 +163,20 @@ def reverse(
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
.. versionadded:: 1.18.0

:param str lang: language of returned ``name`` element (the pseudo
language code 'local' will return it in local language)
Full list of supported languages ISO639-2 can be found here:
https://www.geonames.org/countries/

:param str find_nearby_type: A flag to switch between different
endpoints. The default value is ``findNearbyPlaceNameJSON`` which
was the only option before geopy 1.18 and returns the closest
populated place. Another currently implemented option is
``findNearbyJSON`` return the closest toponym for the lat/lng query

:rtype: ``None``, :class:`geopy.location.Location` or a list of them, if
``exactly_one=False``.

Expand All @@ -171,13 +198,68 @@ def reverse(
'lng': lng,
'username': self.username
}
url = "?".join((self.api_reverse, urlencode(params)))
logger.debug("%s.reverse: %s", self.__class__.__name__, url)
if feature_code:
params['featureCode'] = feature_code

if lang:
params['lang'] = lang
if find_nearby_type == 'findNearbyJSON':
if lang:
raise ValueError("Not supported argument for this api")
url = "?".join((self.api_reverse_nearby, urlencode(params)))
elif find_nearby_type == 'findNearbyPlaceNameJSON':
if feature_code:
raise ValueError("Not supported argument for this api")
url = "?".join((self.api_reverse, urlencode(params)))
else:
raise GeocoderQueryError(
'%s type is not supported by geopy yet' % find_nearby_type
)

return self._parse_json(
self._call_geocoder(url, timeout=timeout),
exactly_one
)

def reverse_timezone(self, query, timeout=DEFAULT_SENTINEL):
"""
Find the timezone a point in `query`.

.. versionadded:: 1.18.0

: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.timezone: %s", self.__class__.__name__, url)
response = self._call_geocoder(url, timeout=timeout)

return self._parse_json_timezone(response)

def _parse_json_timezone(self, response):
return from_timezone_name(response["timezoneId"], raw=response)

def _parse_json(self, doc, exactly_one):
"""
Parse JSON response body.
Expand Down
85 changes: 83 additions & 2 deletions test/geocoders/geonames.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# -*- coding: UTF-8 -*-
import unittest

from pytz import timezone

from geopy import Point
from geopy.compat import u
from geopy.geocoders import GeoNames
from test.geocoders.util import GeocoderTestBase, env
Expand All @@ -23,11 +26,18 @@ def test_user_agent_custom(self):
class GeoNamesTestCase(GeocoderTestBase):

delta = 0.04
new_york_point = Point(40.75376406311989, -73.98489005863667)
america_new_york = timezone("America/New_York")

@classmethod
def setUpClass(cls):
cls.geocoder = GeoNames(username=env['GEONAMES_USERNAME'])

def reverse_timezone_run(self, payload, expected):
timezone = self._make_request(self.geocoder.reverse_timezone, **payload)
self.assertEqual(timezone.pytz_timezone, expected)
return timezone

def test_unicode_name(self):
self.geocode_run(
{"query": "Mount Everest, Nepal"},
Expand All @@ -45,6 +55,77 @@ def test_query_urlencoding(self):

def test_reverse(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test contains too many checks. Could you please split it to multiple distinct test methods?

self.reverse_run(
{"query": "40.75376406311989, -73.98489005863667", "exactly_one": True},
{"latitude": 40.75376406311989, "longitude": -73.98489005863667},
{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
},
{
"latitude": 40.75376406311989,
"longitude": -73.98489005863667,
"address": "Times Square, NY, US",
},
)

with self.assertRaises(ValueError):
self.reverse_run(
{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
"feature_code": 'ADM1'
},
{},
)

loc = self._make_request(
self.geocoder.reverse,
**{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
"lang": 'ru'
}
)
self.assertEqual(loc.raw['adminName1'], 'Нью-Йорк')

def test_find_nearby_json(self):
with self.assertRaises(ValueError):
self.reverse_run(
{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
"find_nearby_type": 'findNearbyJSON',
"lang": 'en',
},
{},
)

self.reverse_run(
{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
"find_nearby_type": 'findNearbyJSON',
},
{
"latitude": 40.75376406311989,
"longitude": -73.98489005863667,
"address": "Bryant Park Studios, NY, US",
},
)

self.reverse_run(
{
"query": "40.75376406311989, -73.98489005863667",
"exactly_one": True,
"find_nearby_type": 'findNearbyJSON',
"feature_code": "ADM1"
},
{
"latitude": 40.16706,
"longitude": -74.49987,
},
)

def test_reverse_timezone(self):
self.reverse_timezone_run(
{"query": self.new_york_point},
self.america_new_york,
)