Android Open Source - ClusteredMarkers Maps Activity






From Project

Back to project page ClusteredMarkers.

License

The source code is released under:

MIT License

If you think the Android project ClusteredMarkers listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package de.kartheininger.markerclustering;
// w  w w .j  a v  a2 s  . c  o  m
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import de.kartheininger.markerclustering.config.Constants;
import de.kartheininger.markerclustering.markercluster.ClusterObject;
import de.kartheininger.markerclustering.markercluster.MarkerObject;

/*
*   Copyright (c) 2013 Kartheininger Johannes
*   Permission is hereby granted, free of charge, to any person obtaining a copy of this software
*   and associated documentation files (the "Software"), to deal in the Software without
*   restriction, including without limitation the rights to use, copy, modify, merge, publish,
*   distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
*   Software is furnished to do so, subject to the following conditions:
*   The above copyright notice and this permission notice shall be included in all copies or
*   substantial portions of the Software.
*
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
*   BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
*   DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/


public class MapsActivity extends AbstractMapActivity implements GoogleMap.OnMarkerClickListener {

    private static final String TAG = MapsActivity.class.getSimpleName();

    private GoogleMap mMap;
    private Context context;


    private HashSet<MarkerObject> setA = new HashSet<MarkerObject>();  // set of all annotations that may be displayed
    private HashSet<MarkerObject> setC = new HashSet<MarkerObject>();  // set of clusters to be displayed
    private HashSet<MarkerObject> setV = new HashSet<MarkerObject>();  // subset of A of those annotations that are visible within the bounds/span of the map window.

    private HashMap<String, LatLngBounds> markerReferenceList;

    private AsyncTask mTask;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps_main);
        if (readyToGo()) {
            this.context = this;
            //Initiate a referenceList for later use to choose the right Bounds on MarkerClick
            this.markerReferenceList = new HashMap<String, LatLngBounds>();
            measureMap();
            setUpMapIfNeeded();
        }
    }


    @Override
    protected void onResume() {
        super.onResume();
        if (readyToGo()) {
            setUpMapIfNeeded();
        }
    }

    /**
     * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly
     * installed) and the map has not already been instantiated.. This will ensure that we only ever
     * call {@link #setUpMap()} once when {@link #mMap} is not null.
     * <p/>
     * If it isn't installed {@link SupportMapFragment} (and
     * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to
     * install/update the Google Play services APK on their device.
     * <p/>
     * A user can return to this FragmentActivity after following the prompt and correctly
     * installing/updating/enabling the Google Play services. Since the FragmentActivity may not have been
     * completely destroyed during this process (it is likely that it would only be stopped or
     * paused), {@link #onCreate(Bundle)} may not be called again so we should call this method in
     * {@link #onResume()} to guarantee that it will be called.
     */
    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }

    /**
     * This is where we can add markers or lines, add listeners or move the camera.
     * This should only be called once and when we are sure that {@link #mMap} is not null.
     */
    private void setUpMap() {
        // Just for the user's convenience place the map center to a place where the test data objects are located
        // and set some reasonable region.
        // The initial span may vary from iPhone to iPad
        //TODO: Smartphone Tablet Zoom wie Herrmann...

//        map.animateCamera(CameraUpdateFactory.zoomTo(zoom), 1500, null);
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(48.697616, 9.165135), 7));
        mMap.setOnCameraChangeListener(getCameraChangeListener());
        mMap.setOnMarkerClickListener(this);

        // Read the sample data from json file and then add all to setA
        ArrayList<String[]> allLocations = readJson();

        // Create set A - with all annotations
        for (String[] location : allLocations) {
            String number, postcode, city, name, street;
            float lon, lat;
            //number      = location[0];
            postcode = location[1];
            city = location[2];
            name = location[3];
            lon = Float.parseFloat(location[4]);
            lat = Float.parseFloat(location[5]);
            //street      = location[6];

            MarkerObject markerObject = new MarkerObject(this);
            markerObject.addMarkerOptions(new MarkerOptions().position(new LatLng(lat, lon))
                    .title(name).snippet(postcode + " " + city));

            setA.add(markerObject);
            // We dont want to add the marker here right now
        }
    }

    //Listener is called, when Cameraposition on the map changed.
    // Equivalent to regionDidChangeAnimated on iOS
    private GoogleMap.OnCameraChangeListener getCameraChangeListener() {
        return new GoogleMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition position) {
//                if another asynctask has been started, cancel it, cause we dont need his computation
//                anymore... cause we have a new cameraposition with new visible markers/clusters
//                and another problem could be that we can get a ConcurrentModificationException,
//                cause we change setC concurrent in more than one asynctask
                if (mTask != null){
                    mTask.cancel(true);
                }

                //Give the visible area to an async task and let them do almost the rest
                loadMarkersTask loader = new loadMarkersTask();
                mTask = loader.execute(mMap.getProjection().getVisibleRegion().latLngBounds);
            }
        };
    }

    //Here are finally our markers drawn onto the map
    //Get called from AsyncTask
    private void drawMapMarkers()
    {
        //Before adding any annotations remove the existing ones
        mMap.clear();

        //Clear our referenceList, so that we can fill it with the actual values
        markerReferenceList.clear();

        Marker markerReference;
        for (MarkerObject allObjects : setC) {
            markerReference = mMap.addMarker(allObjects.getMarkerOptions());
            markerReferenceList.put(markerReference.getId(), allObjects.getBounds());
        }
    }


    @Override
    public boolean onMarkerClick(final Marker marker) {
        if (marker.getTitle().equals(Constants.CLUSTERMARKER)){
            animateCameraTo(mMap, markerReferenceList.get(marker.getId()));
            return true;
        }
        //If clicked normal marker, do default-onMarkerClick
        return false;
    }


    private class loadMarkersTask extends AsyncTask<LatLngBounds, Void, Void>
    {
        @Override
        protected Void doInBackground(LatLngBounds... params) {
        LatLngBounds latLngBounds = params[0];
        visibleMarkers(latLngBounds);
        clusterMarkers(latLngBounds);
//            addAllMarkers();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            //All markers were read, our markeroptions are generated, and now lets finally draw them on the map :)
            drawMapMarkers();
        }


        // This method determines which markers of setA are visible within the current span of the mapView
        // It creates a new set with those markers and returns it.
        private void visibleMarkers(LatLngBounds mapBounds) {
            setV.clear();

            //Determine the bounding box which corresponds to the map views' currently spanned area.
//            LatLngBounds mapBounds = mMap.getProjection().getVisibleRegion().latLngBounds;

            int maxMarkersVisible;
            if (isTablet(context)) {
                maxMarkersVisible = Constants.kNTMaxMarkersInMapTablet;
            } else {
                maxMarkersVisible = Constants.kNTMaxMarkersInMapPhone;
            }

            for (MarkerObject allMarkerObjects : setA) {
                if (mapBounds.contains(allMarkerObjects.getPosition())) {
                    // Add the marker but check, whether the number of markers exceeds the maximum.
                    // In that case just empty the array/set and return.
                    if (setV.size() >= maxMarkersVisible) {
                        setV.clear();
                        return;
                    }
                    setV.add(allMarkerObjects);
                }
            }
        }


        private void clusterMarkers(LatLngBounds mapBounds) {
            // Empty the setC
            setC.clear();

            int maxMarkersInCluster;
            if (isTablet(context)) {
                maxMarkersInCluster = Constants.kNTMaxMarkersInClusterTablet;
            } else {
                maxMarkersInCluster = Constants.kNTMaxMarkersInClusterPhone;
            }

            for (MarkerObject marker : setV) {
                ClusterObject clusterFound = new ClusterObject(context);

                // Iterate over a copy of the set because we change the contents of
                // the set probably even several times per loop.
                HashSet<MarkerObject> copyOfSetC = new HashSet<MarkerObject>(setC);
                for (MarkerObject clusterMarker : copyOfSetC) {
                    if (geoTouchMarker(clusterMarker, marker, mapBounds)) {
                        // A match was found!
                        // If this is the first match then the marker just needs to be combined with the
                        // touching marker or cluster.
                        if (clusterFound.isEmpty()) {
                            // This is the first touch
                            // If the touching marker is a cluster then we just need to add the current
                            // marker. If it is a single marker then a new cluster must be created and added
                            // to setC while the single marker must be removed.
                            if (clusterMarker.isCluster()) {
                                // Remember the current cluster for potential unifications with more clusters or annotations
                                // that may be found touching marker
                                clusterFound = (ClusterObject) clusterMarker;

                                //Add the marker to the cluster
                                ((ClusterObject) clusterMarker).addMarkerOptions(marker.getMarkers());
                            } else {
                                // replace the single marker in setC by a new cluster.
                                ClusterObject newCluster = new ClusterObject(context);
                                newCluster.addMarkerOptions(clusterMarker.getMarkers());
                                newCluster.addMarkerOptions(marker.getMarkers());
                                clusterFound = newCluster;
                                setC.add(newCluster);
                                setC.remove(clusterMarker);
                                }
                        } else {
                            // This is a second (or more) touch.
                            // The touching marker or cluster needs to be combined with the cluster that
                            // has been found earlier and removed from setC.
                            clusterFound.addMarkerOptions(clusterMarker.getMarkers());
                            setC.remove(clusterMarker);
                        }
                    }   //touch was found
                }   //iteration over setC

                // if no touches were found then the marker needs to be added to setC as a single marker.
                if (clusterFound.isEmpty()) {
                    setC.add(marker);
                } else {
                    // If a cluster was found then the number of markers in that cluster was increased, regardless
                    // whether a single marker was added or a number of clusters and markers were unified.
                    // If now the maximum number of markers in a cluster is exeeded then no marker is to be
                    // shown at all.
                    // Therefore empty setV and return.
                    if (clusterFound.getMarkers().size() >= maxMarkersInCluster) {
                        setV.clear();
                        return;
                    }

                }   //iteration over setV
            }
        }

        private boolean geoTouchMarker(MarkerObject firstMarker, MarkerObject secondMarker, LatLngBounds mapBounds) {

            double latDiff = Math.abs(firstMarker.getPosition().latitude - secondMarker.getPosition().latitude);
            double lonDiff = Math.abs(firstMarker.getPosition().longitude - secondMarker.getPosition().longitude);

            float itemsVertical = mapsFragmentHeight / Constants.kNTAnnotationSizeY;
            float itemsHorizontal = mapsFragmentWidth / Constants.kNTAnnotationSizeX;

            double latitudeDelta = Math.abs(mapBounds.southwest.latitude - mapBounds.northeast.latitude);
            double longitudeDelta = Math.abs(mapBounds.southwest.longitude - mapBounds.northeast.longitude);

            boolean doesTouch = (latitudeDelta / itemsVertical > latDiff) && (longitudeDelta / itemsHorizontal > lonDiff);
            return doesTouch;
        }

    }



}




Java Source Code List

de.kartheininger.markerclustering.AbstractMapActivity.java
de.kartheininger.markerclustering.MapsActivity.java
de.kartheininger.markerclustering.config.Constants.java
de.kartheininger.markerclustering.markercluster.ClusterObject.java
de.kartheininger.markerclustering.markercluster.MarkerObject.java