diff --git a/lib/python/heremaps/__init__.py b/lib/python/heremaps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python/heremaps/heremapsexceptions.py b/lib/python/heremaps/heremapsexceptions.py new file mode 100644 index 0000000..6416872 --- /dev/null +++ b/lib/python/heremaps/heremapsexceptions.py @@ -0,0 +1,22 @@ +#!/usr/local/bin/python +# -*- coding: utf-8 -*- + +import json + +class BadGeocodingParams(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr('Bad geocoding params: ' + json.dumps(value)) + +class NoGeocodingParams(Exception): + def __str__(self): + return repr('No params for geocoding specified') + +class EmptyGeocoderResponse(Exception): + def __str__(self): + return repr('The request could not be geocoded') + +class MalformedResult(Exception): + def __str__(self): + return repr('Result structure is malformed') diff --git a/lib/python/heremaps/heremapsgeocoder.py b/lib/python/heremaps/heremapsgeocoder.py new file mode 100644 index 0000000..696c79f --- /dev/null +++ b/lib/python/heremaps/heremapsgeocoder.py @@ -0,0 +1,110 @@ +#!/usr/local/bin/python +# -*- coding: utf-8 -*- + +import json +import urllib + +from heremaps.heremapsexceptions import BadGeocodingParams +from heremaps.heremapsexceptions import EmptyGeocoderResponse +from heremaps.heremapsexceptions import NoGeocodingParams +from heremaps.heremapsexceptions import MalformedResult + +class Geocoder: + 'A Here Maps Geocoder wrapper for python' + + URL_GEOCODE_JSON = 'http://geocoder.api.here.com/6.2/geocode.json' + DEFAULT_MAXRESULTS = 1 + DEFAULT_GEN = 9 + + ADDRESS_PARAMS = [ + 'city', + 'country', + 'county', + 'district', + 'housenumber', + 'postalcode', + 'searchtext', + 'state', + 'street' + ] + + ADMITTED_PARAMS = [ + 'additionaldata', + 'app_id', + 'app_code', + 'bbox', + 'countryfocus', + 'gen', + 'jsonattributes', + 'jsoncallback', + 'language', + 'locationattributes', + 'locationid', + 'mapview', + 'maxresults', + 'pageinformation', + 'politicalview', + 'prox', + 'strictlanguagemode' + ] + ADDRESS_PARAMS + + app_id = '' + app_code = '' + maxresults = '' + + def __init__(self, app_id, app_code, maxresults=DEFAULT_MAXRESULTS, gen=DEFAULT_GEN): + self.app_id = app_id + self.app_code = app_code + self.maxresults = maxresults + self.gen = gen + + def geocode(self, params): + if not set(params.keys()).issubset(set(self.ADDRESS_PARAMS)): + raise BadGeocodingParams(params) + + response = self.perform_request(params) + + try: + results = response['Response']['View'][0]['Result'] + except IndexError: + raise EmptyGeocoderResponse() + + return results + + def perform_request(self, params): + request_params = { + 'app_id' : self.app_id, + 'app_code' : self.app_code, + 'maxresults' : self.maxresults, + 'gen' : self.gen + } + request_params.update(params) + + encoded_request_params = urllib.urlencode(request_params) + + response = json.load( + urllib.urlopen(self.URL_GEOCODE_JSON + + '?' + + encoded_request_params)) + + return response + + def geocode_address(self, **kwargs): + params = {} + for key, value in kwargs.iteritems(): + if value: params[key] = value + + if not params: raise NoGeocodingParams() + + return self.geocode(params) + + def extract_lng_lat_from_result(self, result): + try: + location = result['Location'] + except KeyError: + raise MalformedResult() + + longitude = location['DisplayPosition']['Longitude'] + latitude = location['DisplayPosition']['Latitude'] + + return [longitude, latitude] diff --git a/lib/python/heremaps/tests/__init__.py b/lib/python/heremaps/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/python/heremaps/tests/heremapsgeocoder_tests.py b/lib/python/heremaps/tests/heremapsgeocoder_tests.py new file mode 100644 index 0000000..6fbd1c0 --- /dev/null +++ b/lib/python/heremaps/tests/heremapsgeocoder_tests.py @@ -0,0 +1,126 @@ +#!/usr/local/bin/python +# -*- coding: utf-8 -*- + +import unittest + +from heremaps import heremapsgeocoder +from heremaps.heremapsexceptions import BadGeocodingParams +from heremaps.heremapsexceptions import EmptyGeocoderResponse +from heremaps.heremapsexceptions import NoGeocodingParams +from heremaps.heremapsexceptions import MalformedResult + +from secrets import * + +class GeocoderTestCase(unittest.TestCase): + EMPTY_RESPONSE = { + "Response":{ + "MetaInfo":{ + "Timestamp":"2015-11-04T16:31:57.273+0000" + }, + "View":[] + } + } + + GOOD_RESPONSE = { + "Response": { + "MetaInfo": { + "Timestamp":"2015-11-04T16:30:32.187+0000" + }, + "View":[{ + "_type":"SearchResultsViewType", + "ViewId":0, + "Result":[{ + "Relevance":0.89, + "MatchLevel":"street", + "MatchQuality":{ + "City":1.0, + "Street":[1.0] + }, + "Location":{ + "LocationId":"NT_yyKB4r3mCWAX4voWgxPcuA", + "LocationType":"address", + "DisplayPosition":{ + "Latitude":40.43433, + "Longitude":-3.70126 + }, + "NavigationPosition":[{ + "Latitude":40.43433, + "Longitude":-3.70126 + }], + "MapView":{ + "TopLeft":{ + "Latitude":40.43493, + "Longitude":-3.70404 + }, + "BottomRight":{ + "Latitude":40.43373, + "Longitude":-3.69873 + } + }, + "Address":{ + "Label":"Calle de Eloy Gonzalo, Madrid, España", + "Country":"ESP", + "State":"Comunidad de Madrid", + "County":"Madrid", + "City":"Madrid", + "District":"Trafalgar", + "Street":"Calle de Eloy Gonzalo", + "AdditionalData":[{ + "value":"España", + "key":"CountryName" + }, + { + "value":"Comunidad de Madrid", + "key":"StateName" + }, + { + "value":"Madrid", + "key":"CountyName" + }] + } + } + }] + }] + } + } + + def setUp(self): + self.geocoder = heremapsgeocoder.Geocoder(None, None) + + def test_geocode_address_with_valid_params(self): + self.geocoder.perform_request = lambda x: self.GOOD_RESPONSE + response = self.geocoder.geocode_address( + searchtext='Calle Eloy Gonzalo 27', + city='Madrid', + country='España') + + def test_geocode_address_with_invalid_params(self): + with self.assertRaises(BadGeocodingParams): + self.geocoder.geocode_address( + searchtext='Calle Eloy Gonzalo 27', + manolo='escobar') + + def test_geocode_address_with_no_params(self): + with self.assertRaises(NoGeocodingParams): + self.geocoder.geocode_address() + + def test_geocode_address_empty_response(self): + self.geocoder.perform_request = lambda x: self.EMPTY_RESPONSE + with self.assertRaises(EmptyGeocoderResponse): + self.geocoder.geocode_address(searchtext='lkajfñlasjfñ') + + def test_extract_lng_lat_from_result(self): + result = self.GOOD_RESPONSE['Response']['View'][0]['Result'][0] + coordinates = self.geocoder.extract_lng_lat_from_result(result) + + self.assertEqual(coordinates[0], -3.70126) + self.assertEqual(coordinates[1], 40.43433) + + def test_extract_lng_lat_from_result_with_malformed_result(self): + result = {'manolo':'escobar'} + + with self.assertRaises(MalformedResult): + self.geocoder.extract_lng_lat_from_result(result) + +if __name__ == '__main__': + main()