Geocode addresses in a dataset. The dataset must have a string field
        with the addresses to geocode and a geometry field (points) for the
        geocoding results.
        :param project_id: Must be a string.
        :param dataset_id: Must be a string.
        :param address_field: Name of the address field in the dataset.
        :param geometry_field: Name of the geometry field in the dataset.
        :param extra_params: Dictionary to filter the Geocoding response.
                       For example: {'country':'PE'}
                       More information:
                       https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering

def geocode_addresses(self, project_id, dataset_id, address_field,
                          geometry_field, **extra_params):
        """
        Geocode addresses in a dataset. The dataset must have a string field
        with the addresses to geocode and a geometry field (points) for the
        geocoding results.
        :param project_id: Must be a string.
        :param dataset_id: Must be a string.
        :param address_field: Name of the address field in the dataset.
        :param geometry_field: Name of the geometry field in the dataset.
        :param extra_params: Dictionary to filter the Geocoding response.
                       For example: {'country':'PE'}
                       More information:
                       https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering
        """

        project_url = ('/projects/{project_id}'
                       ).format(project_id=project_id)

        dataset_url = ('{project_url}/datasets/{dataset_id}'
                       ).format(project_url=project_url,
                                dataset_id=dataset_id)

        project_query_url = ('{project_url}/sql'
                             ).format(project_url=project_url)

        dataset_data = self.get(dataset_url)
        dataset_count = dataset_data['feature_count']

        print('%d rows to process' % dataset_count)
        print('Estimated time: %d minutes' % (dataset_count * 2 / 60))
        start = datetime.now()
        print('Started at %s hrs' % start.strftime('%H:%M'))

        get_query = ('SELECT {address_column}, amigo_id '
                     'FROM dataset_{dataset_id}'
                     ).format(address_column=address_field,
                              dataset_id=dataset_id)
        rows = []

        print('Exporting addresses...')

        for i in range(0, dataset_count, 1000):
            response = self.get(
                project_query_url,
                params={
                    'query': get_query,
                    'offset': i,
                    'limit': 1000
                }
            )
            dataset_rows = response['data']
            rows.extend(dataset_rows)

        print('Done!')
        print('Geocoding addresses...')

        geocoder_url = '/me/geocoder/search'
        geocoder_params = {'focus.point.lat': 0, 'focus.point.lon': 0}

        components = ''

        for key, value in extra_params.items():
            components += '{key}:{value}|'.format(key=key, value=value)

        geocoder_params['components'] = components[:-1]

        def geocode_address(row_data):
            address = row_data[address_field]
            amigo_id = row_data['amigo_id']

            geocoder_params['text'] = address
            geocoder_result = self.get(geocoder_url,
                                       params=geocoder_params,
                                       stream=True)

            if geocoder_result.status_code == 200:
                coordinates = json.loads(geocoder_result.text)[
                    'features'][0]['geometry']['coordinates']
                lng = str(coordinates[0])
                lat = str(coordinates[1])

                return ("('{amigo_id}', "
                        "ST_SetSRID(ST_MakePoint({lng}, {lat}), 4326)),"
                        ).format(amigo_id=amigo_id,
                                 lng=lng,
                                 lat=lat)

            return ''

        processed = 0
        steps = 30
        for i in range(0, len(rows), steps):
            rows_to_geocode = rows[i: i + steps]
            threads = []

            for row in rows_to_geocode:
                threads.append(gevent.spawn(geocode_address,
                                            row))
            gevent.joinall(threads)
            values = ''.join([thread.value for thread in threads])

            if values != '':
                data = {
                    'query': ('UPDATE dataset_{dataset_id} as d '
                              'SET {geo_column} = c.{geo_column} '
                              'FROM (values {values}) '
                              'as c(amigo_id, {geo_column}) '
                              'WHERE c.amigo_id = d.amigo_id'
                              ).format(dataset_id=dataset_id,
                                       geo_column=geometry_field,
                                       values=values[:-1])
                }

                self.post(project_query_url, data=data)

            processed += len(rows_to_geocode)
            print('%d%%' % (float(processed) / dataset_count * 100))

        count_query = ('SELECT count(*) '
                       'FROM dataset_{dataset_id} '
                       'WHERE {geo_column} IS NOT NULL'
                       ).format(dataset_id=dataset_id,
                                geo_column=geometry_field)
        points_count = self.get(
            project_query_url,
            params={'query': count_query})['data'][0]['count']

        print('Done!')
        print('Finished at %s hrs' % datetime.now().strftime('%H:%M'))
        print('Success rate: %d of %d points created' %
              (points_count, dataset_count))
        total_time = datetime.now() - start
        print('Total time: %s' % total_time)
        average_time = total_time.total_seconds() / dataset_count
        print('Average time per request: %.3f seconds' % average_time)