'Geocode an Address
- used by S3LocationSelector
settings.get_gis_geocode_imported_addresses
@param address: street address
@param postcode: postcode
@param Lx_ids: list of ancestor IDs
@param geocoder: which geocoder service to use'

@staticmethod
def geocode(address, postcode=None, Lx_ids=None, geocoder='google'):

    from geopy import geocoders
    if ((geocoder == 'google') or (geocoder is True)):
        g = geocoders.GoogleV3()
        if current.gis.google_geocode_retry:
            import time
            from geopy.geocoders.googlev3 import GTooManyQueriesError
            def geocode_(names, g=g, **kwargs):
                attempts = 0
                while (attempts < 3):
                    try:
                        result = g.geocode(names, **kwargs)
                    except GTooManyQueriesError:
                        if (attempts == 2):
                            current.gis.google_geocode_retry = False
                            raise
                        time.sleep(1)
                    else:
                        break
                    attempts += 1
                return result
        else:
            geocode_ = (lambda names, g=g, **kwargs: g.geocode(names, **kwargs))
    elif (geocoder == 'yahoo'):
        apikey = current.deployment_settings.get_gis_api_yahoo()
        g = geocoders.Yahoo(apikey)
        geocode_ = (lambda names, g=g, **kwargs: g.geocode(names, **kwargs))
    else:
        raise NotImplementedError
    location = address
    if postcode:
        location = ('%s,%s' % (location, postcode))
    Lx = L5 = L4 = L3 = L2 = L1 = L0 = None
    if Lx_ids:
        table = current.s3db.gis_location
        limit = len(Lx_ids)
        if (limit > 1):
            query = table.id.belongs(Lx_ids)
        else:
            query = (table.id == Lx_ids[0])
        db = current.db
        Lx = db(query).select(table.id, table.name, table.level, table.gis_feature_type, limitby=(0, limit), orderby=(~ table.level))
        if Lx:
            Lx_names = ','.join([l.name for l in Lx])
            location = ('%s,%s' % (location, Lx_names))
            for l in Lx:
                if (l.level == 'L0'):
                    L0 = l.id
                    continue
                elif (l.level == 'L1'):
                    L1 = l.id
                    continue
                elif (l.level == 'L2'):
                    L2 = l.id
                    continue
                elif (l.level == 'L3'):
                    L3 = l.id
                    continue
                elif (l.level == 'L4'):
                    L4 = l.id
                    continue
                elif (l.level == 'L5'):
                    L5 = l.id
            Lx = Lx.as_dict()
    try:
        results = geocode_(location, exactly_one=False)
        if (len(results) == 1):
            (place, (lat, lon)) = results[0]
            if Lx:
                output = None
                results = geocode_(Lx_names, exactly_one=False)
                if (not results):
                    output = "Can't    check    that    these    results    are    specific    enough"
                for result in results:
                    (place2, (lat2, lon2)) = result
                    if (place == place2):
                        output = 'We    can    only    geocode    to    the    Lx'
                        break
                if (not output):
                    L0_row = None
                    wkt = None
                    if (L5 and (Lx[L5]['gis_feature_type'] != 1)):
                        wkt = db((table.id == L5)).select(table.wkt, limitby=(0, 1)).first().wkt
                        used_Lx = L5
                    elif (L4 and (Lx[L4]['gis_feature_type'] != 1)):
                        wkt = db((table.id == L4)).select(table.wkt, limitby=(0, 1)).first().wkt
                        used_Lx = L4
                    elif (L3 and (Lx[L3]['gis_feature_type'] != 1)):
                        wkt = db((table.id == L3)).select(table.wkt, limitby=(0, 1)).first().wkt
                        used_Lx = L3
                    elif (L2 and (Lx[L2]['gis_feature_type'] != 1)):
                        wkt = db((table.id == L2)).select(table.wkt, limitby=(0, 1)).first().wkt
                        used_Lx = L2
                    elif (L1 and (Lx[L1]['gis_feature_type'] != 1)):
                        wkt = db((table.id == L1)).select(table.wkt, limitby=(0, 1)).first().wkt
                        used_Lx = L1
                    elif L0:
                        L0_row = db((table.id == L0)).select(table.wkt, table.lon_min, table.lat_min, table.lon_max, table.lat_max, limitby=(0, 1)).first()
                        if (not L0_row.wkt.startswith('POI')):
                            wkt = L0_row.wkt
                        used_Lx = L0
                    if wkt:
                        from shapely.geometry import point
                        from shapely.wkt import loads as wkt_loads
                        try:
                            from shapely import speedups
                            speedups.enable()
                        except:
                            current.log.info('S3GIS', 'Upgrade    Shapely    for    Performance    enhancements')
                        test = point.Point(lon, lat)
                        shape = wkt_loads(wkt)
                        ok = test.intersects(shape)
                        if (not ok):
                            output = ('Returned    value    not    within    %s' % Lx[used_Lx]['name'])
                    elif L0:
                        if (not L0_row):
                            L0_row = db((table.id == L0)).select(table.lon_min, table.lat_min, table.lon_max, table.lat_max, limitby=(0, 1)).first()
                        if ((lat < L0_row['lat_max']) and (lat > L0_row['lat_min']) and (lon < L0_row['lon_max']) and (lon > L0_row['lon_min'])):
                            ok = True
                        else:
                            ok = False
                            output = ('Returned    value    not    within    %s' % Lx['name'])
                    else:
                        ok = True
                    if ok:
                        output = dict(lat=lat, lon=lon)
            else:
                output = dict(lat=lat, lon=lon)
        elif len(results):
            output = 'Multiple    results    found'
        else:
            output = 'No    results    found'
    except:
        error = sys.exc_info()[1]
        output = str(error)
    return output
