5

I recently downloaded tagged hospital data from OpenStreetMaps.org using the API. Some of the data have a 'node' tag which are attributed with a lat/long while other data are tagged as 'way' format without an address attribute. This leaves me with several hospitals that only have a 'name' attribute and no address/lat-long geolocation.

I am wondering if there are public APIs that can be accessed using Python and allow me to pass just a hospital name like, 'CityA Hospital', and either get an exact address or a lat/long.

4

I have a script I use to take a list of addresses and geocode them to points in a new feature class. You just need to download the requests module and supply a service url for a Geocoding REST service. I do not really know of any open source geocoding services though.

import requests
import json
import arcpy
import os
import sys
arcpy.env.overwriteOutput = True

def Message(msg):
    print str(msg)
    arcpy.AddMessage(str(msg))
    return msg

def assertJsonSuccess(data):
    import json
    obj = json.loads(data)
    if 'status' in obj and obj['status'] == "error":
        Message("Error: JSON object returns an error. " + str(obj))
        return False
    else:
        return True

def geocode_addresses(new_fc, addresses=[], serviceURL='', sr=3857):
    import json
    import requests
    """Geocodes a list of addresses using a Geocode Service and returns a
    point feature class

    Quick and dirty geocode.  There may be a limit on how many addresses can be
    used before the server rejects the requests

    Required:
    new_fc -- new point feature class for geocoded addresses
    addresses -- dictionary of address and feature name {'address' : 'feature description',..}
                 or a list of addresses ['address, city, state, zip'...]
    serviceURL -- url to a REST endpoint for a Geocoding service
    sr -- spatial reference or WKID for new feature class (should be same as geocoding service)

    Addresses can be in one of the below formats. 

    # example: pass addresses with description as dictionary
    addresses = {"400 N 1st Ave W, Hartley, IA, 51346" : "Prins Laundromat",
                 "120 S 8th Ave W, Hartley, IA, 51346" : "Hengeveld Construction",
                 "173 S Central Ave, Hartley, IA, 51346" : "Legal Eyes"}

    # example: pass addresses in as a list
    addresses = ["901 N Broadway, Saint Louis, Missouri, 63102",
                 "10701 Lambert International Blvd, Saint Louis,
                 "15193 Olive Blvd, Chesterfield, Missouri, 63017"]
    """
    # check to see if addresses are provided as dict
    hasFeature = True
    if not isinstance(addresses, dict):
        addresses = dict((a, '') for a in addresses)
        hasFeature = False

    # dictionary to store addresses and points
    addr = {}
    bad = []
    for address, name in addresses.iteritems():
        # This request only needs the single line address as text and the response formatting parameter .
        params = {'text': address, 'f': 'pjson'}
        headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}

        # Connect to service to get its current JSON definition.
        r = requests.post(serviceURL, params=params, headers=headers)
        if (r.status_code != 200):
            Message("Could not geocode address {0}.\r\nhttp status code {1}.".format(address, r.status_code))
            bad.append(address)
        else:
            if not assertJsonSuccess(r.text):
                Message('Error when reading service information:  {0}'.format(r.text))
                bad.append(address)

            #Get the first returned location
            loc = False
            candidates = r.json()
            if 'locations' in candidates:
                if candidates['locations']:
                    loc = True
                    candidate = candidates['locations'][0]
                    geo = candidate['feature']['geometry']

                    # Create (X,Y) tuple and Point objects and score
                    pt = (geo['x'], geo['y'])
                    score = candidate['feature']['attributes']['Score']
                    addr[(address, name)] = (arcpy.PointGeometry(arcpy.Point(*pt), prj), score)
            if not loc:
                Message("Could not geocode address \"{0}\"".format(address))

    # create new feature class
    f_length = min([max(len(a) for a in addresses), 255])
    if arcpy.Exists(new_fc):
        arcpy.Delete_management(new_fc)
    arcpy.CreateFeatureclass_management(os.path.dirname(new_fc),
                                        os.path.basename(new_fc),
                                        'POINT', spatial_reference=sr)
    arcpy.AddField_management(new_fc, 'Address', 'TEXT', field_length=f_length)
    arcpy.AddField_management(new_fc, 'Feature', 'TEXT', field_length=100)
    arcpy.AddField_management(new_fc, 'Score', 'SHORT')

    # insert rows 
    with arcpy.da.InsertCursor(new_fc, ['SHAPE@', 'Address', 'Feature', 'Score']) as rows:
        for attributes, pt in addr.iteritems():
            add, name = attributes
            xy, score = pt
            rows.insertRow([xy, add, name, score])
            Message('Geocoded address: {0}'.format(add))


    # Delete feature field if no feature names supplied
    if not hasFeature:
        arcpy.DeleteField_management(new_fc, ['Feature'])

    # if bad records
    if bad:
        date = time.strftime('_%m_%d_%Y')
        txt = r'C:\Users\{0}\Desktop\Geocode_fail_{1}.txt'.format(os.environ['USERNAME'], date)
        with open(txt, 'w') as f:
            f.writelines('\n'.join(bad))
        os.startfile(txt)
    return new_fc
  • This code works well when there is a defined address for each hospital. However, I am dealing with only hospital names (e.g Milford Hospital). Is there a way to geocode addresses with only the name of the hospital and not an actual address? – DavBar Oct 6 '14 at 16:41
  • 1
    This is pretty cool, I just found a free python api for geocoding and reverse geocoding addresses. Take a look at this page, it should do what you want. I tested with a couple hospital names and it returned coordinates. It uses the google maps Geocoder: code.xster.net/pygeocoder/wiki/Home – crmackey Oct 6 '14 at 18:09
2

Mapzen has a really cool geocoding service that allows you to query the name of the place to get an XY coordinate. (https://mapzen.com/documentation/search/search/). You can get a free API key from their developers page which will allow you to make 30,000 queries per day at 6 per second. (https://mapzen.com/)

Here's a function that will return the XY location for the query (with string input). You can change it to return answer to look at all of the resulting data (including address). Also, at the end of the path string, you can remove the &size=1 if you want to get back all possible results instead of just one.

import json
import requests
import urllib

def geocode(address):
    api = 'YOUR_API_KEY_HERE'
    addresses =address
    base = 'https://search.mapzen.com'
    path = '{}/v1/search?api_key={}&text={}&size=1'.format(base,api, urllib.quote_plus(address))
    response = requests.get(path, verify=False)
    answer = response.json()
    return answer['features'][0]['geometry']['coordinates']

Example:

 >>> geocode("golds gym, austin, tx")
[-97.83996, 30.223798]

EDIT: Just tried your "Milford Hospital" example. (in XY, not lat/long)

>>> geocode("Milford Hospital")
[-0.625876, 51.167924]

UPDATE: Sadly Mapzen is no longer active, though the code is on GitHub if you wanted to host your own. For the function above, you would have to replace the base and path variables to reflect whatever geocoding API you want to use (like google), format the URL properly, and set your return value to be the X and Y values returned in the response.json().

  • 1
    After some trial and error, this works wonderfully well...thanks for sharing!! – DPSSpatial Nov 21 '17 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.