Android Cluster manager icon depending on type
Asked Answered
S

1

7

I'm working on a small app and have implemented Google Maps and Places api. Currently i'm able to see all my markers on the map and clustering working fine. I'm able to zoom in the clusters open up and able to see the markers. I have a spinner that has different types and once selected that type is passed to the places search string.

This is my maps code that includes the clustering:

public class MapsActivity extends FragmentActivity implements LocationListener,ClusterManager.OnClusterItemInfoWindowClickListener<MyItem> {

    GoogleMap mMap;
    double myLatitude = 0;
    double myLongitude = 0;

    HashMap<String, String> mMarker = new HashMap<String, String>();
    PlaceJSONParser placeJsonParser = new PlaceJSONParser();

    private ClusterManager<MyItem> mClusterManager;
    protected MyItem clickedClusterItem;

    String[] placeType;
    String[] placeTypeName;
    Spinner spinPlaceType;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);


        mMap = mapFragment.getMap();
        onMapReady();

        // Array of place types
        placeType = getResources().getStringArray(R.array.placeType);

        // Array of place type names
        placeTypeName = getResources().getStringArray(R.array.placeTypeName);

        // Creating an array adapter with an array of Place types
        // to populate the spinner
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,  R.layout.spinner_item, R.id.textview, placeTypeName);

        // Getting reference to the Spinner
        spinPlaceType = (Spinner) findViewById(R.id.spinPlaceType);

        // Setting adapter on Spinner to set place types
        spinPlaceType.setAdapter(adapter);

        spinPlaceType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

                int selectedPosition = spinPlaceType.getSelectedItemPosition();
                final String type = placeType[selectedPosition];

                StringBuilder sb = new StringBuilder(
                        "https://maps.googleapis.com/maps/api/place/nearbysearch/json?");
                sb.append("location=" + myLatitude + "," + myLongitude);
                sb.append("&type=" + type);
                sb.append("&radius=4000");
                sb.append("&key=PLACES_KEY");
                // Creating a new non-ui thread task to download Google place json
                // data
                PlacesTask placesTask = new PlacesTask();

                // Invokes the "doInBackground()" method of the class PlaceTask
                placesTask.execute(sb.toString());

            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                StringBuilder sb = new StringBuilder(
                        "https://maps.googleapis.com/maps/api/place/nearbysearch/json?");
                sb.append("location=" + myLatitude + "," + myLongitude);
                sb.append("&type=restaurant");
                sb.append("&radius=4000");
                sb.append("&key=PLACES_KEY");
                // Creating a new non-ui thread task to download Google place json
                // data
                PlacesTask placesTask = new PlacesTask();

                // Invokes the "doInBackground()" method of the class PlaceTask
                placesTask.execute(sb.toString());
            }
        });


        // Will display next 20 places returned form the next_page_token
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab_more);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Finding you some more places.", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();

                StringBuilder sb = new StringBuilder(
                        "https://maps.googleapis.com/maps/api/place/nearbysearch/json?");
                sb.append("pagetoken=" + placeJsonParser.getNext_Page_token());
                sb.append("&key=PLACES_KEY");
                // Creating a new non-ui thread task to download Google place json
                // data

                if (placeJsonParser.getNext_Page_token() == null || placeJsonParser.getNext_Page_token() == ""){
                    Snackbar.make(view, "No more places left to find.", Snackbar.LENGTH_SHORT)
                            .setAction("Action", null).show();
                }

                PlacesTask placesTask = new PlacesTask();

                // Invokes the "doInBackground()" method of the class PlaceTask
                placesTask.execute(sb.toString());
            }
        });

        mMap.setOnInfoWindowClickListener(new OnInfoWindowClickListener() {
            @Override
            public void onInfoWindowClick(Marker marker) {
                Intent detailsIntent = new Intent(getBaseContext(), PlaceDetailsActivity.class);
                String reference = mMarker.get(marker.getId());
                marker.getPosition();
                detailsIntent.putExtra("reference", reference);
                detailsIntent.putExtra("markerLat", myLatitude);
                detailsIntent.putExtra("markerLong", myLongitude);
                startActivity(detailsIntent);
            }
        });

    }

    public void onMapReady(){
        // Enabling MyLocation in Google Map
        mMap.setMyLocationEnabled(true);
        mMap.getUiSettings().setCompassEnabled(true);
        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Getting LocationManager object from System Service
        // LOCATION_SERVICE
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        // Creating a criteria object to retrieve provider
        Criteria criteria = new Criteria();

        // Getting the name of the best provider
        String provider = locationManager.getBestProvider(criteria, true);

        // Getting Current Location From GPS
        Location location = locationManager.getLastKnownLocation(provider);

        // onLocationChanged(location);
        if (location != null) {
            onLocationChanged(location);
        }
    }

    /**
     * A method to download json data from url
     */
    private String downloadUrl(String strUrl) throws IOException {
        String referer ="";
        StringBuilder jsonResults = new StringBuilder();
        HttpURLConnection conn = null;
        try {
            URL url = new URL(strUrl);

            // Creating an http connection to communicate with url
            conn = (HttpURLConnection) url.openConnection();
            if (referer != null) {
                conn.setRequestProperty("Referer", referer);
            }

            InputStreamReader in = new InputStreamReader(conn.getInputStream());

            // Load the results into a StringBuilder
            int read;
            char[] buff = new char[1024];
            while ((read = in.read(buff)) != -1) {
                jsonResults.append(buff, 0, read);
            }
            // Displays the list of places found in the terminal.
            Log.i("Data", "Places Found: " + jsonResults);
        } catch (MalformedURLException e) {
            Log.i("Google Places Utility", "Error processing Places API URL");
            return null;
        } catch (IOException e) {
            Log.i("Google Places Utility", "Error connecting to Places API");
            return null;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        return jsonResults.toString();

    }


    /**
     * A class, to download Google Places
     */
    private class PlacesTask extends AsyncTask<String, Integer, String> {

        String data = null;

        // Invoked by execute() method of this object
        @Override
        protected String doInBackground(String... url) {
            try {
                data = downloadUrl(url[0]);
            } catch (Exception e) {
                Log.d("Background Task", e.toString());
            }
            return data;
        }

        // Executed after the complete execution of doInBackground() method
        @Override
        protected void onPostExecute(String result) {
            ParserTask parserTask = new ParserTask();

            // Start parsing the Google places in JSON format
            // Invokes the "doInBackground()" method of the class ParseTask
            parserTask.execute(result);
        }

    }

    /**
     * A class to parse the Google Places in JSON format
     */
    private class ParserTask extends
            AsyncTask<String, Integer, List<HashMap<String, String>>> {

        JSONObject jObject;

        // Invoked by execute() method of this object
        @Override
        protected List<HashMap<String, String>> doInBackground(
                String... jsonData) {

            List<HashMap<String, String>> places = null;

            try {
                jObject = new JSONObject(jsonData[0]);

                /** Getting the parsed data as a List construct */
                places = placeJsonParser.parse(jObject);

            } catch (Exception e) {
                Log.d("Exception", e.toString());
            }
            return places;
        }

        // Executed after the complete execution of doInBackground() method
        @Override
        protected void onPostExecute(List<HashMap<String, String>> list) {

            // Clears all the existing markers
            mMap.clear();
            setUpClusterer(list);

        }
    }

    private void setUpClusterer(List<HashMap<String, String>> list) {

        // Position the map.
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(myLatitude,myLongitude), 13));

        // Initialize the manager with the context and the map.
        // (Activity extends context, so we can pass 'this' in the constructor.)
        mClusterManager = new ClusterManager<MyItem>(this, mMap);

        // Point the map's listeners at the listeners implemented by the cluster
        // manager.
        mMap.setOnCameraChangeListener(mClusterManager);
        mMap.setOnMarkerClickListener(mClusterManager);

        mMap.setInfoWindowAdapter(mClusterManager.getMarkerManager());

        mMap.setOnInfoWindowClickListener(mClusterManager);
        mClusterManager.setOnClusterItemInfoWindowClickListener(this);

        mClusterManager
                .setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyItem>() {
                    @Override
                    public boolean onClusterItemClick(MyItem item) {
                        clickedClusterItem = item;
                        return false;
                    }
                });
        // Add cluster items (markers) to the cluster manager.
        addItems(list);

        mClusterManager.getMarkerCollection().setOnInfoWindowAdapter(
                new MyCustomAdapterForItems());
    }

    public class MyCustomAdapterForItems implements GoogleMap.InfoWindowAdapter {

        private final View myContentsView;

        MyCustomAdapterForItems() {
            myContentsView = getLayoutInflater().inflate(
                    R.layout.info_window, null);
        }
        @Override
        public View getInfoWindow(Marker marker) {

            TextView tvTitle = ((TextView) myContentsView
                    .findViewById(R.id.txtTitle));
            TextView tvSnippet = ((TextView) myContentsView
                    .findViewById(R.id.txtSnippet));

            tvTitle.setText(clickedClusterItem.getTitle());
            tvSnippet.setText(clickedClusterItem.getSnippet());

            return myContentsView;
        }

        @Override
        public View getInfoContents(Marker marker) {
            return null;
        }
    }

    private void addItems(List<HashMap<String, String>> list) {
        double latitude;
        double longitude;

        for (int i = 0; i < list.size(); i++) {
            HashMap<String, String> hmPlace = list.get(i);

            // Getting latitude of the place
            latitude = Double.parseDouble(hmPlace.get("lat"));

            // Getting longitude of the place
            longitude = Double.parseDouble(hmPlace.get("lng"));

            String name = hmPlace.get("place_name");

            // Getting vicinity
            String vicinity = hmPlace.get("vicinity");
            MyItem offsetItem = new MyItem(latitude, longitude, hmPlace.get("reference"), name, vicinity);
            mClusterManager.addItem(offsetItem);

        }
    }

    public void onClusterItemInfoWindowClick(MyItem item) {
        Intent placesIntent = new Intent(getBaseContext(), PlaceDetailsActivity.class);
        String reference = item.getReference();

        placesIntent.putExtra("name", item.getTitle());
        placesIntent.putExtra("reference", reference);
        placesIntent.putExtra("sourcelat", myLatitude);
        placesIntent.putExtra("sourcelng", myLongitude);
        startActivity(placesIntent);
    }

    @Override
    public void onLocationChanged(Location location) {
        myLatitude = location.getLatitude();
        myLongitude = location.getLongitude();
        LatLng myLocation = new LatLng(myLatitude, myLongitude);
        mMap.moveCamera(CameraUpdateFactory.newLatLng(myLocation));
        mMap.animateCamera(CameraUpdateFactory.zoomTo(13));

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }
}

my myItem class to get info for the markers:

package com.example.tariq.outandabout;

import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.ClusterItem;

public class MyItem implements ClusterItem {
    LatLng mPosition;
    private String reference,placeTitle,snippet;

    public MyItem(double lat, double lng,String val,String title, String snip) {
        mPosition = new LatLng(lat, lng);
        reference=val;
        placeTitle=title;
        snippet = snip;
    }

    @Override
    public LatLng getPosition() {
        // TODO Auto-generated method stub
        return mPosition;
    }
    public String getReference() {
        // TODO Auto-generated method stub
        return reference;
    }
    public String getTitle() {
        // TODO Auto-generated method stub
        return placeTitle;
    }

    public String getSnippet() {
        // TODO Auto-generated method stub
        return snippet;
    }
}

Currently only red markers are getting displayed but I was wondering if there is a way to have a different markers depending on the type selected from the spinner, For example if I select Hospital then the markers are shown as little hospital icons, if I select ATM, then a little ATM icon appears.

Any help will be appreciated.

Subscript answered 9/4, 2016 at 20:28 Comment(0)
W
9

Firstly you have to store all the info you need (at this situation just LatLng and marker icon) insite ClusterItem object.

public class MarkerItem implements ClusterItem {
private String title;
private String snippet;
private LatLng latLng;
private BitmapDescriptor icon;

public MarkerItem(MarkerOptions markerOptions) {
    this.latLng = markerOptions.getPosition();
    this.title = markerOptions.getTitle();
    this.snippet = markerOptions.getSnippet();
    this.icon = markerOptions.getIcon();
}

@Override
public LatLng getPosition() {
    return latLng;
}

public String getTitle() {
    return title;
}

public String getSnippet() {
    return snippet;
}

public void setLatLng(LatLng latLng) {
    this.latLng = latLng;
}

public BitmapDescriptor getIcon() {
    return icon;
}

public void setIcon(BitmapDescriptor icon) {
    this.icon = icon;
}
}

The next step would be to make cluster renderer show your icon instead of default maker icon. To achieve that, you need to extend DefaultClusterRenderer object:

public class ClusterRenderer extends DefaultClusterRenderer<MarkerItem> {

public ClusterRenderer(Context context, GoogleMap map, ClusterManager<MarkerItem> clusterManager) {
    super(context, map, clusterManager);
    clusterManager.setRenderer(this);
}


@Override
protected void onBeforeClusterItemRendered(MarkerItem markerItem, MarkerOptions markerOptions) {
    if (markerItem.getIcon() != null) {
        markerOptions.icon(markerItem.getIcon()); //Here you retrieve BitmapDescriptor from ClusterItem and set it as marker icon
    }
    markerOptions.visible(true);
}
}

Finally, you have to initialize the clusterRenderer and markerItems

ClusterManager clusterManager = new ClusterManager<>(context, googleMap);
ClusterRenderer clusterRenderer = new ClusterRenderer<>(activity, googleMap, clusterManager); // not needed to use clusterManager.setRenderer method since i made it in constructor
MarkerOptions markerOptions = new MarkerOptions()
            .position(new LatLng(latitude, longitude))
            .icon(BitmapDescriptorFactory.fromResource(R.drawable.your_resource_icon));
MarkerItem markerItem = new MarkerItem(markerOptions);
clusterManager.addItem(markerItem);

You can implement your own logic, which icon you want to pass to markerItem here.

EDIT
To pass different icons, you could create a separate method for that
Example:

public MarkerOptions getMarkerOptions(LatLng latLng, String title, String snippet,  int iconRes) {
    return new MarkerOptions()
            .title(title)
            .snippet(snippet)
            .position(latLng)
            .icon(BitmapDescriptorFactory.fromResource(iconRes));
}

EDIT 2 I updated MarkerItem class to suit your needs, replace your MyItem class with MarkerItem class. Add your items using this class and update it to suit your needs

Waggish answered 11/4, 2016 at 7:4 Comment(9)
Thank you for the reply! Where would that code fit exactly? Or would it all be a new separate class?Subscript
ClusterRenderer and MarkerItem could be a separate public class, and the other initialization code goes in activity or wherever you initialize your mapAntiparallel
And in terms of displaying icons for different types would that just be a set of if statements? So for example if (types=atm)... then the rest of the icon stuffSubscript
and that would go in an If statement for each icon?Subscript
I'm a little confused as to where I initialize the markers in my code. I have a setUpClusterer class and an addItem class in the code displayed in my original postSubscript
Initialize ClusterManager and ClusterRenderer in Activity onCreate method. You can create your MarkerItems the same way as you do in your addItems methodAntiparallel
Getting errors for context and activity - Edited my original postSubscript
In your code i cannot see you initialized clusterRenderer as i shown; replace your MyItem class with MarkerItem class i providedAntiparallel
I can replace it but then i get errors in my onClusterItemInfoWindowClick method as i want to show the name and address of the place when i click on the markerSubscript

© 2022 - 2024 — McMap. All rights reserved.