diff --git a/CHANGELOG.md b/CHANGELOG.md index 7184722e..af976e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Table of releases * added bing batch forward & reverse geocoding * added mapquest batch geocoding * fixed google ratelimits +* added geocode.xyz support ## [1.32.1] - 2017-09-16 diff --git a/geocoder/__init__.py b/geocoder/__init__.py index cef6d70d..1f8d70a9 100755 --- a/geocoder/__init__.py +++ b/geocoder/__init__.py @@ -37,7 +37,7 @@ from geocoder.api import nokia, osm, tomtom, geolytica, arcgis, opencage, locationiq # noqa from geocoder.api import maxmind, ipinfo, freegeoip, ottawa, here, baidu, gaode, w3w # noqa from geocoder.api import yandex, mapzen, komoot, tamu, geocodefarm, tgos, uscensus # noqa -from geocoder.api import gisgraphy # noqa +from geocoder.api import gisgraphy, geocodexyz # noqa # EXTRAS from geocoder.api import timezone, elevation, places, ip, canadapost, reverse, distance, location # noqa diff --git a/geocoder/api.py b/geocoder/api.py index 546d4cd1..4c3607bf 100755 --- a/geocoder/api.py +++ b/geocoder/api.py @@ -13,6 +13,7 @@ from geocoder.freegeoip import FreeGeoIPQuery from geocoder.gaode import GaodeQuery from geocoder.geocodefarm import GeocodeFarmQuery +from geocoder.geocodexyz import GeocodeXYZQuery from geocoder.geolytica import GeolyticaQuery from geocoder.gisgraphy import GisgraphyQuery from geocoder.here import HereQuery @@ -173,6 +174,9 @@ 'geocode': GisgraphyQuery, 'reverse': GisgraphyReverse, }, + 'geocodexyz': { + 'geocode': GeocodeXYZQuery, + }, } @@ -381,6 +385,14 @@ def geolytica(location, **kwargs): return get(location, provider='geolytica', **kwargs) +def geocodexyz(location, **kwargs): + """Geocode.xyz Provider + + :param ``location``: Your search location you want geocoded. + """ + return get(location, provider='geocodexyz', **kwargs) + + def opencage(location, **kwargs): """Opencage Provider diff --git a/geocoder/geocodexyz.py b/geocoder/geocodexyz.py new file mode 100644 index 00000000..8b958dbe --- /dev/null +++ b/geocoder/geocodexyz.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# coding: utf8 +from __future__ import absolute_import + +import logging + +from geocoder.base import OneResult, MultipleResultsQuery +from geocoder.keys import geocodexyz_key + + +class GeocodeXYZResult(OneResult): + + def _get_value(self, json_obj, key, type=None): + value = json_obj.get(key, {}) + if value: + value = value.strip() + if type and value: + return type(value) + return value + + def __init__(self, json_content): + # create safe shortcuts + self._standard = json_content.get('standard', {}) + + # proceed with super.__init__ + super(GeocodeXYZResult, self).__init__(json_content) + + @property + def lat(self): + return self._get_value(self.raw, 'latt', float) + + @property + def lng(self): + return self._get_value(self.raw, 'longt', float) + + @property + def remaining_credits(self): + return self._get_value(self.raw, 'remaining_credits', int) + + @property + def confidence(self): + return self._get_value(self._standard, 'confidence', float) + + @property + def country(self): + return self._get_value(self._standard, 'countryname') + + @property + def country_code(self): + return self._get_value(self._standard, 'prov') + + @property + def city(self): + return self._get_value(self._standard, 'city') + + @property + def region(self): + return self._get_value(self._standard, 'region') + + @property + def street(self): + return self._get_value(self._standard, 'addresst') + + @property + def postal(self): + return self._get_value(self._standard, 'postal') + + @property + def housenumber(self): + return self._get_value(self._standard, 'stnumber') + + @property + def address(self): + if self.street_number: + return u'{0} {1}, {2}'.format( + self.street_number, self.route, self.locality) + elif self.route and self.route != 'un-known': + return u'{0}, {1}'.format(self.route, self.locality) + else: + return self.locality + + +class GeocodeXYZQuery(MultipleResultsQuery): + """API to retrieve data from geocode.xyz + + Geocode.xyz uses only open data sources, including but not limited to + OpenStreetMap, Geonames, Osmnames, openaddresses.io, UK Ordnance Survey, + www.dati.gov.it, data.europa.eu/euodp/en/data, PSMA Geocoded National + Address File (Australia), etc. + + """ + provider = 'geocodexyz' + method = 'geocode' + + _URL = 'https://geocode.xyz/' + _RESULT_CLASS = GeocodeXYZResult + _KEY = geocodexyz_key + _KEY_MANDATORY = False + + def _build_params(self, location, provider_key, **kwargs): + params = { + 'json': 1, + 'locate': location, + } + if 'region' in kwargs: + region = kwargs.pop('region') + if region: + params.update({'region': region}) + if 'strictmode' in kwargs: + params.update({'strictmode': kwargs.pop('strictmode')}) + if 'strict' in kwargs: + params.update({'strict': kwargs.pop('strict')}) + if 'auth' in kwargs: + params.update({'auth': kwargs.pop('auth')}) + else: + params.update({'auth': provider_key}) + return params + + def _adapt_results(self, json_response): + return [json_response] + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + g = GeocodeXYZQuery('1552 Payette dr., Ottawa') + g.debug() diff --git a/geocoder/keys.py b/geocoder/keys.py index 6d42c2d9..c8ae7b97 100644 --- a/geocoder/keys.py +++ b/geocoder/keys.py @@ -15,6 +15,7 @@ baidu_key = os.environ.get('BAIDU_API_KEY') baidu_security_key = os.environ.get('BAIDU_SECURITY_KEY') gaode_key = os.environ.get('GAODE_API_KEY') +geocodexyz_key = os.environ.get('GEOCODEXYZ_API_KEY') w3w_key = os.environ.get('W3W_API_KEY') mapbox_access_token = os.environ.get('MAPBOX_ACCESS_TOKEN') google_key = os.environ.get('GOOGLE_API_KEY') diff --git a/tests/test_geocoder.py b/tests/test_geocoder.py index b409d4e5..8db89668 100644 --- a/tests/test_geocoder.py +++ b/tests/test_geocoder.py @@ -35,6 +35,7 @@ def test_entry_points(): geocoder.tamu geocoder.geocodefarm geocoder.uscensus + geocoder.geocodexyz def test_location(): diff --git a/tests/test_geocodexyz.py b/tests/test_geocodexyz.py new file mode 100644 index 00000000..80aa05bc --- /dev/null +++ b/tests/test_geocodexyz.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# coding: utf8 + +import geocoder + +location = '1552 Payette dr., Ottawa' + + +def test_geocodexyz(): + g = geocoder.geocodexyz(location) + assert g.ok + osm_count, fields_count = g.debug()[0] + assert osm_count >= 5 + assert fields_count >= 11 + assert isinstance(g.confidence, float) + assert isinstance(g.remaining_credits, int)