Alok Patel
Alok Patel

Reputation: 8022

Google places autocompletes API Android : bounds not working

I'm defining my bounds as follow:

private static final LatLngBounds BOUNDS_CHENNAI = new LatLngBounds(
        new LatLng(12.8339547, 80.0817007), new LatLng(13.2611661, 80.33632279999999)); // Chennai city bounds.

Creating Goolge API client as follow:

mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this,this)
            .addApi(Places.GEO_DATA_API)
            .addConnectionCallbacks(this)
            .build();

And using those bounds in adapter as follow:

PendingResult<AutocompletePredictionBuffer> results =
                Places.GeoDataApi
                        .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                mBounds, mPlaceFilter);

According to the documentation is should return me only location within Chennai city, but it returns me location from around the world.

E.g. When I type "Sola" it returns "Sola road" of Ahmedabad city instead of showing matched results within bounds.

Upvotes: 7

Views: 4812

Answers (3)

Leo DroidCoder
Leo DroidCoder

Reputation: 15046

I found a primitive workaround in case you have to show a results of specified country or nearby countries:

private ArrayList<PlaceAutocomplete> getAutocomplete(CharSequence constraint) {
    if (mGoogleApiClient.isConnected()) {

        // Submit the query to the autocomplete API and retrieve a PendingResult that will
        // contain the results when the query completes.
        PendingResult<AutocompletePredictionBuffer> results =
                Places.GeoDataApi
                        .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                mBounds, mPlaceFilter);

        AutocompletePredictionBuffer autocompletePredictions = results
                .await(60, TimeUnit.SECONDS);

        final Status status = autocompletePredictions.getStatus();
        if (status.isSuccess()) {
            Log.i(LOG_TAG, "Query completed. Received " + autocompletePredictions.getCount()
                    + " predictions.");

            // Copy the results into our own data structure, because we can't hold onto the buffer.
            // AutocompletePrediction objects encapsulate the API response (place ID and description).

            Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
            ArrayList<PlaceAutocomplete> resultList = new ArrayList<>(autocompletePredictions.getCount());

            while (iterator.hasNext()) {
                AutocompletePrediction prediction = iterator.next();
                // Get the details of this prediction and copy it into a new PlaceAutocomplete object.
                String data = prediction.getDescription();

                // here we manually checking whether description contains our needed country(ies)
                if (predictionIsInNeededCountry(data)) {
                    resultList.add(new PlaceAutocomplete(prediction.getPlaceId(), prediction.getDescription(),
                            prediction.getPrimaryText(mCharacterStyle), prediction.getSecondaryText(mCharacterStyle),
                            prediction.getFullText(mCharacterStyle)));
                }
            }
        } else {
            Toast.makeText(mContext, "Error contacting API: " + status.toString(),
                    Toast.LENGTH_SHORT).show();
            Log.e(LOG_TAG, "Error getting autocomplete prediction API call: " + status.toString());
            autocompletePredictions.release();
            return null;
        }

        // Release the buffer now that all data has been copied.
        autocompletePredictions.release();

        return resultList;
    }
    Log.e(LOG_TAG, "Google API client is not connected for autocomplete query.");
    return null;
}

private boolean predictionIsInNeededCountry(String data) {

    // here you can add countries (in different languages if you want)
    // also you can try get current country programmatically with use of Geocoder
    if (data.contains("Ukraine") || data.contains("Украина") || data.contains("Україна")) {
        return true;
    }
    return false;
}

For the bounds I use current coordinates (current latitude and longitude for south west and for north east).

Also I implemented another solution:

  1. Get predictions
  2. Get Place details of each prediction (you can pass an array to get all the Points at once).
  3. Compare Point's coordinates with current and sort them from closest to furthest.
  4. Show sorted by distance results.

It seems to work more accurate, but requires additional requests and calculations.
In my case I need only the results from 2 countries so I end up with the first simpler solution.

Upvotes: 0

user3821381
user3821381

Reputation: 114

I have the same problem with the bounds in android. I've tried everything and can't solve the problem.

Looking for other web pages, i found this in the documentation for javascript when trying to set bounds to an area:

The results are biased towards, but not restricted to, Places contained within these bounds.

It looks like the android places api documentation is not complete. We will have to wait.

Upvotes: 6

Teraiya Mayur
Teraiya Mayur

Reputation: 1154

import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.Spanned;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.Place;
import com.google.android.gms.location.places.PlaceBuffer;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;

public class SearchPlacesActivity extends AppCompatActivity implements    GoogleApiClient.OnConnectionFailedListener {


protected GoogleApiClient mGoogleApiClient;

private PlaceAutocompleteAdapter mAdapter;

private AutoCompleteTextView mAutocompleteView;

private TextView mPlaceDetailsText;

private TextView mPlaceDetailsAttribution;
Toolbar toolbar;

private static final LatLngBounds BOUNDS_GREATER_SYDNEY = new LatLngBounds(
        // new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362));
        new LatLng(0, 0), new LatLng(0, 0));

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Construct a GoogleApiClient for the {@link Places#GEO_DATA_API} using AutoManage
    // functionality, which automatically sets up the API client to handle Activity lifecycle
    // events. If your activity does not extend FragmentActivity, make sure to call connect()
    // and disconnect() explicitly.
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this, 0 /* clientId */, this)
            .addApi(Places.GEO_DATA_API)
            .build();

    setContentView(R.layout.activity_search_places);
    //Set toolbar
    toolbar = (Toolbar) findViewById(R.id.maintoolbar);

    toolbar.setTitle("Change Location");

    toolbar.setTitleTextColor(0xFFFFFFFF);

    setSupportActionBar(toolbar);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getSupportActionBar().setHomeButtonEnabled(false);



    // Retrieve the AutoCompleteTextView that will display Place suggestions.
    mAutocompleteView = (AutoCompleteTextView) findViewById(R.id.autocomplete_places);

    // Register a listener that receives callbacks when a suggestion has been selected
    mAutocompleteView.setOnItemClickListener(mAutocompleteClickListener);

    // Retrieve the TextViews that will display details and attributions of the selected place.
    mPlaceDetailsText = (TextView) findViewById(R.id.place_details);
    mPlaceDetailsAttribution = (TextView) findViewById(R.id.place_attribution);

    // Set up the adapter that will retrieve suggestions from the Places Geo Data API that cover
    // the entire world.
    mAdapter = new PlaceAutocompleteAdapter(this, mGoogleApiClient, BOUNDS_GREATER_SYDNEY,
            null);
    mAutocompleteView.setAdapter(mAdapter);

    // Set up the 'clear text' button that clears the text in the autocomplete view
    Button clearButton = (Button) findViewById(R.id.button_clear);
    clearButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mAutocompleteView.setText("");
        }
    });
}

/**
 * Listener that handles selections from suggestions from the AutoCompleteTextView that
 * displays Place suggestions.
 * Gets the place id of the selected item and issues a request to the Places Geo Data API
 * to retrieve more details about the place.
 *
 * @see com.google.android.gms.location.places.GeoDataApi#getPlaceById(GoogleApiClient,
 * String...)
 */
private AdapterView.OnItemClickListener mAutocompleteClickListener
        = new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        /*
         Retrieve the place ID of the selected item from the Adapter.
         The adapter stores each Place suggestion in a AutocompletePrediction from which we
         read the place ID and title.
          */
        final AutocompletePrediction item = mAdapter.getItem(position);
        final String placeId = item.getPlaceId();
        final CharSequence primaryText = item.getPrimaryText(null);

        AppLog.i("Search", "Autocomplete item selected: " + primaryText);

        /*
         Issue a request to the Places Geo Data API to retrieve a Place object with additional
         details about the place.
          */
        PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
                .getPlaceById(mGoogleApiClient, placeId);
        placeResult.setResultCallback(mUpdatePlaceDetailsCallback);

        /*Toast.makeText(getApplicationContext(), "Clicked: " + primaryText,
                Toast.LENGTH_SHORT).show();*/
        AppLog.i("Search", "Called getPlaceById to get Place details for " + placeId);
    }
};

/**
 * Callback for results from a Places Geo Data API query that shows the first place result in
 * the details view on screen.
 */
private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
        = new ResultCallback<PlaceBuffer>() {
    @Override
    public void onResult(PlaceBuffer places) {
        if (!places.getStatus().isSuccess()) {
            // Request did not complete successfully
            AppLog.e("Search", "Place query did not complete. Error: " + places.getStatus().toString());
            places.release();
            return;
        }
        // Get the Place object from the buffer.
        final Place place = places.get(0);

        // Format details of the place for display and show it in a TextView.
    /*    mPlaceDetailsText.setText(formatPlaceDetails(getResources(), place.getName(),
                place.getId(), place.getAddress(), place.getPhoneNumber(),
                place.getWebsiteUri(),place.getLatLng()));*/

        AppLog.e("LAtlng", ">>>>>>>>>>>>" + place.getLatLng());
        LatLng latLng = place.getLatLng();
        double lat = latLng.latitude;
        double lng = latLng.longitude;
SearchFood.searchresult = place.getName().toString() + "," +   place.getAddress().toString();
SearchActivity.searchresult = place.getName().toString() + "," + place.getAddress().toString();

        onBackPressed();
        places.release();
    }
};

private static Spanned formatPlaceDetails(Resources res, CharSequence name, String id,
                                          CharSequence address, CharSequence phoneNumber, Uri websiteUri, LatLng latLng) {
    AppLog.e("Search", res.getString(R.string.place_details, name, id, address, phoneNumber,
            websiteUri));
    return Html.fromHtml(res.getString(R.string.place_details, name, id, address, phoneNumber,
            websiteUri));

}

/**
 * Called when the Activity could not connect to Google Play services and the auto manager
 * could resolve the error automatically.
 * In this case the API is not available and notify the user.
 *
 * @param connectionResult can be inspected to determine the cause of the failure
 */
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {

    AppLog.e("Search", "onConnectionFailed: ConnectionResult.getErrorCode() = "
            + connectionResult.getErrorCode());

    // TODO(Developer): Check error code and notify the user of error state and resolution.
  /*  Toast.makeText(this,
            "Could not connect to Google API Client: Error " + connectionResult.getErrorCode(),
            Toast.LENGTH_SHORT).show();*/
}


@Override
public void onBackPressed() {

    super.onBackPressed();
    SearchPlacesActivity.this.finish();
    overridePendingTransition(R.anim.trans_right_in, R.anim.trans_right_out);

}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == android.R.id.home) {
        onBackPressed();
    }
    return super.onOptionsItemSelected(item);
}
}

// ADAPTER CLASS


import android.content.Context;
import android.graphics.Typeface;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import com.eatcommunity.util.AppLog;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.data.DataBufferUtils;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import   com.google.android.gms.location.places.AutocompletePredictionBuffer;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLngBounds;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

public class PlaceAutocompleteAdapter
    extends ArrayAdapter<AutocompletePrediction> implements Filterable     {

private static final String TAG = "PlaceAutocompleteAdapter";
private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
/**
 * Current results returned by this adapter.
 */
private ArrayList<AutocompletePrediction> mResultList;


private GoogleApiClient mGoogleApiClient;


private LatLngBounds mBounds;
private AutocompleteFilter mPlaceFilter;
public PlaceAutocompleteAdapter(Context context, GoogleApiClient googleApiClient,
                                LatLngBounds bounds, AutocompleteFilter filter) {
    super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1);
    mGoogleApiClient = googleApiClient;
    mBounds = bounds;
    mPlaceFilter = filter;
}
public void setBounds(LatLngBounds bounds) {
    mBounds = bounds;
}
@Override
public int getCount() {
    return mResultList.size();
}
@Override
public AutocompletePrediction getItem(int position) {
    return mResultList.get(position);
}

@Override
public View getView(int position, View convertView, ViewGroup parent)     {
    View row = super.getView(position, convertView, parent);

    // Sets the primary and secondary text for a row.
    // Note that getPrimaryText() and getSecondaryText() return a CharSequence that may contain
    // styling based on the given CharacterStyle.

    AutocompletePrediction item = getItem(position);

    TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
    TextView textView2 = (TextView) row.findViewById(android.R.id.text2);
    textView1.setText(item.getPrimaryText(STYLE_BOLD));
    textView2.setText(item.getSecondaryText(STYLE_BOLD));

    return row;
}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            // Skip the autocomplete query if no constraints are given.
            if (constraint != null) {
                // Query the autocomplete API for the (constraint) search string.
                mResultList = getAutocomplete(constraint);
                if (mResultList != null) {
                    // The API successfully returned results.
                    results.values = mResultList;
                    results.count = mResultList.size();
                }
            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results != null && results.count > 0) {
                // The API returned at least one result, update the data.
                notifyDataSetChanged();
            } else {
                // The API did not return any results, invalidate the data set.


                //notifyDataSetInvalidated();
            }
        }

        @Override
        public CharSequence convertResultToString(Object resultValue) {
            // Override this method to display a readable result in the AutocompleteTextView
            // when clicked.
            if (resultValue instanceof AutocompletePrediction) {
                return ((AutocompletePrediction) resultValue).getFullText(null);
            } else {
                return super.convertResultToString(resultValue);
            }
        }
    };
}
private ArrayList<AutocompletePrediction> getAutocomplete(CharSequence constraint) {
    if (mGoogleApiClient.isConnected()) {
        AppLog.i(TAG, "Starting autocomplete query for: " + constraint);

        // Submit the query to the autocomplete API and retrieve a PendingResult that will
        // contain the results when the query completes.
        PendingResult<AutocompletePredictionBuffer> results =
                Places.GeoDataApi
                        .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
                                mBounds, mPlaceFilter);

        AutocompletePredictionBuffer autocompletePredictions = results
                .await(60, TimeUnit.SECONDS);

        final Status status = autocompletePredictions.getStatus();
        if (!status.isSuccess()) {
           /* Toast.makeText(getContext(), "Error contacting API: " + status.toString(),
                    Toast.LENGTH_SHORT).show();*/
            AppLog.e(TAG, "Error getting autocomplete prediction API call: " + status.toString());
            autocompletePredictions.release();
            return null;
        }

        return DataBufferUtils.freezeAndClose(autocompletePredictions);
    }
    AppLog.e(TAG, "Google API client is not connected for autocomplete query.");
    return null;
}
}




<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/container_toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

<include
android:id="@+id/maintoolbar"
layout="@layout/application_toolbar" />
</LinearLayout>

<ScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@+id/container_toolbar">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Search Places"
android:textAppearance="?  android:attr/textAppearanceMedium" />

<AutoCompleteTextView
android:id="@+id/autocomplete_places"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="Enter your place"
android:singleLine="true" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:src="@drawable/powered_by_google_light"
android:visibility="gone" />

<Button
android:id="@+id/button_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Clear text" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Selelcted Place"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="gone" />

<TextView
android:id="@+id/place_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text=""
android:textAppearance="? android:attr/textAppearanceMedium" />

<TextView
android:id="@+id/place_attribution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:paddingTop="@dimen/activity_vertical_margin"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</ScrollView>
</RelativeLayout>

Menifest File change

        <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="YOURAPIKEY" />

    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

Upvotes: -1

Related Questions