diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f72e92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +geoapi.ini +.idea/ diff --git a/distance-calc.iml b/distance-calc.iml new file mode 100755 index 0000000..ad3c0a3 --- /dev/null +++ b/distance-calc.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/geoapi.ini.template b/geoapi.ini.template new file mode 100755 index 0000000..3211e18 --- /dev/null +++ b/geoapi.ini.template @@ -0,0 +1,12 @@ +[google] +apikey=<> + +[mapquest] +apikey=<> +secret=<> + +[geocodio] +apikey=<> + +[locationiq] +token=<> diff --git a/src/gcoder.py b/src/gcoder.py new file mode 100755 index 0000000..c60413c --- /dev/null +++ b/src/gcoder.py @@ -0,0 +1,119 @@ +# GCoder.py: basic geocoding test + +import sys +import argparse +import json +import configparser +import urllib.parse +import urllib.request + + +class GeocodingError(RuntimeError): + pass + + +def geocode_google(config, address): + apikey = config['google']['apikey'] + if not apikey: + raise GeocodingError("Google API key not specified") + query = { 'key': apikey, 'address': address, 'region': 'us'} + url = 'https://maps.googleapis.com/maps/api/geocode/json?' + \ + urllib.parse.urlencode(query, quote_via=urllib.parse.quote) + with urllib.request.urlopen(url) as response: + if response.status == 200: + apireturn = json.loads(response.read()) + stat = apireturn['status'] + if stat == 'OK': + results = apireturn['results'] + if len(results) > 1: + raise GeocodingError(f"Google API returned ambiguous results (total count {len(results)})") + coords = results[0]['geometry']['location'] + return coords['lat'], coords['lng'] + else: + raise GeocodingError(f"Google API returns status of {stat}") + else: + raise GeocodingError(f"Google API returns {response.status} HTTP status code") + + +def geocode_mapquest(config, address): + apikey = config['mapquest']['apikey'] + if not apikey: + raise GeocodingError("MapQuest API key not specified") + query = { 'key': apikey, 'inFormat': 'kvp', 'outFormat': 'json', 'location': address, + 'thumbMaps': 'false', 'maxResults': 1} + url = 'https://www.mapquestapi.com/geocoding/v1/address?' + urllib.parse.urlencode(query) + with urllib.request.urlopen(url) as response: + if response.status == 200: + apireturn = json.loads(response.read()) + stat = apireturn['info']['statuscode'] + if stat == 0: + results = apireturn['results'] + if len(results) > 1: + raise GeocodingError(f"MapQuest API returned ambiguous results (total count {len(results)})") + locations = results[0]['locations'] + if len(locations) > 1: + raise GeocodingError(f"MapQuest API returned ambiguous locations (total count {len(locations)})") + coords = locations[0]['latLng'] + return coords['lat'], coords['lng'] + else: + raise GeocodingError(f"MapQuest API returns status of {stat}") + else: + raise GeocodingError(f"MapQuest API returns {response.status} HTTP status code") + + +def geocode_geocodio(config, address): + apikey = config['geocodio']['apikey'] + if not apikey: + raise GeocodingError("Geocodio API key not specified") + query = {'q': address, 'api_key': apikey} + url = 'https://api.geocod.io/v1.6/geocode?' + urllib.parse.urlencode(query) + with urllib.request.urlopen(url) as response: + if response.status == 200: + apireturn = json.loads(response.read()) + coords = apireturn['results'][0]['location'] + return coords['lat'], coords['lng'] + else: + raise GeocodingError(f"Geocodio API returns {response.status} HTTP status code") + + +def geocode_locationiq(config, address): + apikey = config['locationiq']['token'] + if not apikey: + raise GeocodingError("LocationIQ API key not specified") + query = {'q': address, 'key': apikey, 'format': 'json', 'limit': 1} + url = 'https://us1.locationiq.com/v1/search.php?' + urllib.parse.urlencode(query, quote_via=urllib.parse.quote) + with urllib.request.urlopen(url) as response: + if response.status == 200: + apireturn = json.loads(response.read()) + place = apireturn[0] + return place['lat'], place['lon'] + else: + raise GeocodingError(f"LocationIQ API returns {response.status} HTTP status code") + + +geocoding_procs = { + 'google': geocode_google, + 'mapquest': geocode_mapquest, + 'geocodio': geocode_geocodio, + 'locationiq': geocode_locationiq +} + +cmdline_parser = argparse.ArgumentParser() +cmdline_parser.add_argument('address', nargs='+', help='The address to be geocoded') +cmdline_parser.add_argument('-C', '--config', default='geoapi.ini', help='The geocoding API configuration file') +cmdline_parser.add_argument('-g', '--geocoder', default='google', choices=geocoding_procs.keys(), + help='Geocoding processor to use to get coordinates (default: google)') + + +def main(args): + opts = cmdline_parser.parse_args(args) + config = configparser.ConfigParser() + config.read(opts.config) + my_address = ' '.join(opts.address) + print(f"Address: '{my_address}'") + coords = geocoding_procs[opts.geocoder](config, my_address) + print(f"Coordinates: Latitude {coords[0]}, longitude {coords[1]}") + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))