Android Open Source - clusterkraf Clusterkraf






From Project

Back to project page clusterkraf.

License

The source code is released under:

Apache License

If you think the Android project clusterkraf 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 com.twotoasters.clusterkraf;
//  w  ww. j  a  va 2s .c  o m
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;

import android.view.View;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CancelableCallback;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

/**
 * Clusters InputPoint objects on a GoogleMap
 */
public class Clusterkraf {

  private final WeakReference<GoogleMap> mapRef;
  private final Options options;
  private final ClusterTransitionsAnimation transitionsAnimation;

  private final InnerCallbackListener innerCallbackListener;

  private final ArrayList<InputPoint> points = new ArrayList<InputPoint>();
  private ArrayList<ClusterPoint> currentClusters;
  private ArrayList<Marker> currentMarkers;
  private HashMap<Marker, ClusterPoint> currentClusterPointsByMarker = new HashMap<Marker, ClusterPoint>();
  private ArrayList<ClusterPoint> previousClusters;
  private ArrayList<Marker> previousMarkers;
  private BaseClusteringTaskHost clusteringTaskHost;
  private ClusterTransitionsBuildingTaskHost clusterTransitionsBuildingTaskHost;

  /**
   * Construct a Clusterkraf instance to manage your map with customized
   * options and a list of points
   * 
   * @param map
   *            The GoogleMap to be managed by Clusterkraf
   * @param options
   *            Customized options
   */
  public Clusterkraf(GoogleMap map, Options options, ArrayList<InputPoint> points) {
    this.mapRef = new WeakReference<GoogleMap>(map);
    this.options = options;
    this.innerCallbackListener = new InnerCallbackListener(this);
    this.transitionsAnimation = new ClusterTransitionsAnimation(map, options, innerCallbackListener);

    if (points != null) {
      this.points.addAll(points);
    }

    if (map != null) {
      map.setOnCameraChangeListener(innerCallbackListener.clusteringOnCameraChangeListener);
      map.setOnMarkerClickListener(innerCallbackListener);
      map.setOnInfoWindowClickListener(innerCallbackListener);
            map.setInfoWindowAdapter(innerCallbackListener);
    }

    showAllClusters();
  }

  /**
   * Add a single InputPoint for clustering
   * 
   * @param inputPoint
   *            The InputPoint object to be clustered
   */
  public void add(InputPoint inputPoint) {
    // @TODO: test individually adding points
    if (inputPoint != null) {
      points.add(inputPoint);
      updateClustersAndTransition();
    }
  }

  /**
   * Add a list of InputPoint objects for clustering
   * 
   * @param inputPoints
   *            The list of InputPoint objects for clustering
   */
  public void addAll(ArrayList<InputPoint> inputPoints) {
    if (inputPoints != null) {
      points.addAll(inputPoints);
      updateClustersAndTransition();
    }
  }

  /**
   * Remove all existing InputPoint objects and add a new list of InputPoint
   * objects for clustering
   * 
   * @param inputPoints
   *            The new list of InputPoint objects for clustering
   */
  public void replace(ArrayList<InputPoint> inputPoints) {
    clear();
    addAll(inputPoints);
  }

  /**
   * Remove all Clusterkraf-managed markers from the map
   */
  public void clear() {
    /**
     * cancel the background thread clustering task
     */
    if (clusteringTaskHost != null) {
      clusteringTaskHost.cancel();
      clusteringTaskHost = null;
    }
    /**
     * cancel the background thread transition building task
     */
    if (clusterTransitionsBuildingTaskHost != null) {
      clusterTransitionsBuildingTaskHost.cancel();
      clusterTransitionsBuildingTaskHost = null;
    }

    /**
     * we avoid GoogleMap.clear() so users can manage their own
     * non-clustered markers on the map.
     * 
     * @see http://code.google.com/p/gmaps-api-issues/issues/detail?id=4703
     */
    if (currentMarkers != null) {
      for (Marker marker : currentMarkers) {
        marker.remove();
      }
    }
    
    currentClusters = null;
    currentClusterPointsByMarker = null;
    currentMarkers = null;
    previousClusters = null;
    previousMarkers = null;
    
    points.clear();
  }

  // TODO: support removing individual InputPoint objects

  private void drawMarkers() {
    GoogleMap map = mapRef.get();
    if (map != null && currentClusters != null) {
      currentMarkers = new ArrayList<Marker>(currentClusters.size());
      currentClusterPointsByMarker = new HashMap<Marker, ClusterPoint>(currentClusters.size());
      MarkerOptionsChooser moc = options.getMarkerOptionsChooser();
      for (ClusterPoint clusterPoint : currentClusters) {
        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(clusterPoint.getMapPosition());
        if (moc != null) {
          moc.choose(markerOptions, clusterPoint);
        }
        Marker marker = map.addMarker(markerOptions);
        currentMarkers.add(marker);
        currentClusterPointsByMarker.put(marker, clusterPoint);
      }
    }
  }

  private void removePreviousMarkers() {
    GoogleMap map = mapRef.get();
    if (map != null && previousClusters != null && previousMarkers != null) {
      for (Marker marker : previousMarkers) {
        marker.remove();
      }
      previousMarkers = null;
      previousClusters = null;
    }
  }

  private void updateClustersAndTransition() {
    previousClusters = currentClusters;
    previousMarkers = currentMarkers;

    startClusteringTask();
  }

  private void startClusteringTask() {
    clusteringTaskHost = new UpdateClustersAndTransitionClusteringTaskHost();
    clusteringTaskHost.executeTask();
  }

  private void transitionClusters(ClusterTransitions clusterTransitions) {
    if (clusterTransitions != null) {
      transitionsAnimation.animate(clusterTransitions);
    }
    clusterTransitionsBuildingTaskHost = null;
  }

  private void startClusterTransitionsBuildingTask(Projection projection) {
    clusterTransitionsBuildingTaskHost = new ClusterTransitionsBuildingTaskHost();
    clusterTransitionsBuildingTaskHost.executeTask(projection);

    clusteringTaskHost = null;
  }

  private void showAllClusters() {
    if (clusteringTaskHost != null) {
      drawMarkers();
      clusteringTaskHost = null;
    } else {
      clusteringTaskHost = new ShowAllClustersClusteringTaskHost();
      clusteringTaskHost.executeTask();
    }
  }

  /**
   * Animate the camera so all of the InputPoint objects represented by the
   * passed ClusterPoint are in view
   * 
   * @param clusterPoint
   */
  public void zoomToBounds(ClusterPoint clusterPoint) {
    GoogleMap map = mapRef.get();
    if (map != null && clusterPoint != null) {
      innerCallbackListener.clusteringOnCameraChangeListener.setDirty(System.currentTimeMillis());
      CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(clusterPoint.getBoundsOfInputPoints(), options.getZoomToBoundsPadding());
      map.animateCamera(cameraUpdate, options.getZoomToBoundsAnimationDuration(), null);
    }
  }

  /**
   * Show the InfoWindow for the passed Marker and ClusterPoint
   * 
   * @param marker
   * @param clusterPoint
   */
  public void showInfoWindow(Marker marker, ClusterPoint clusterPoint) {
    GoogleMap map = mapRef.get();
    if (map != null && marker != null && clusterPoint != null) {
      long dirtyUntil = System.currentTimeMillis() + options.getShowInfoWindowAnimationDuration();
      innerCallbackListener.clusteringOnCameraChangeListener.setDirty(dirtyUntil);
      CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(marker.getPosition());
      map.animateCamera(cameraUpdate, options.getShowInfoWindowAnimationDuration(), new CancelableCallback() {

        @Override
        public void onFinish() {
          innerCallbackListener.handler.post(new Runnable() {

            @Override
            public void run() {
              innerCallbackListener.clusteringOnCameraChangeListener.setDirty(0);

            }
          });
        }

        @Override
        public void onCancel() {
          innerCallbackListener.clusteringOnCameraChangeListener.setDirty(0);
        }
      });
      marker.showInfoWindow();
    }
  }

  private static class InnerCallbackListener implements ClusteringOnCameraChangeListener.Host, ClusterTransitionsAnimation.Host, OnMarkerClickListener,
      OnInfoWindowClickListener, GoogleMap.InfoWindowAdapter {

    private final WeakReference<Clusterkraf> clusterkrafRef;

    private final Handler handler = new Handler();

    private InnerCallbackListener(Clusterkraf clusterkraf) {
      clusterkrafRef = new WeakReference<Clusterkraf>(clusterkraf);
      clusteringOnCameraChangeListener = new ClusteringOnCameraChangeListener(this, clusterkraf.options);
    }

    private final ClusteringOnCameraChangeListener clusteringOnCameraChangeListener;

    @Override
    public void onClusteringCameraChange() {
      Clusterkraf clusterkraf = clusterkrafRef.get();
      if (clusterkraf != null) {
        if (clusterkraf.clusteringTaskHost != null) {
          clusterkraf.clusteringTaskHost.cancel();
          clusterkraf.clusteringTaskHost = null;
        }
        if (clusterkraf.clusterTransitionsBuildingTaskHost != null) {
          clusterkraf.clusterTransitionsBuildingTaskHost.cancel();
          clusterkraf.clusterTransitionsBuildingTaskHost = null;
        }
        clusterkraf.transitionsAnimation.cancel();
        clusterkraf.updateClustersAndTransition();
      }
    }

    /**
     * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host#
     *      onClusterTransitionStarting()
     */
    @Override
    public void onClusterTransitionStarting() {
      clusteringOnCameraChangeListener.setDirty(System.currentTimeMillis());
    }

    /**
     * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host#
     *      onClusterTransitionStarted()
     */
    @Override
    public void onClusterTransitionStarted() {
      Clusterkraf clusterkraf = clusterkrafRef.get();
      if (clusterkraf != null) {
        /**
         * now that the first frame of the transition has been drawn, we
         * can remove our previous markers without suffering any
         * blinking markers
         */
        clusterkraf.removePreviousMarkers();
      }
    }

    /**
     * @see com.twotoasters.clusterkraf.ClusterTransitionsAnimation.Host#
     *      onClusterTransitionFinished()
     */
    @Override
    public void onClusterTransitionFinished() {
      Clusterkraf clusterkraf = clusterkrafRef.get();
      if (clusterkraf != null) {
        clusterkraf.drawMarkers();
        /**
         * now that we have drawn our new set of markers, we can let the
         * transitionsAnimation know so it can clear its markers
         */
        clusterkraf.transitionsAnimation.onHostPlottedDestinationClusterPoints();
      }
      clusteringOnCameraChangeListener.setDirty(0);
    }

    /**
     * @see com.google.android.gms.maps.GoogleMap.OnMarkerClickListener#onMarkerClick
     *      (com.google.android.gms.maps.model.Marker)
     */
    @Override
    public boolean onMarkerClick(Marker marker) {
      boolean handled = false;
      boolean exempt = false;
      Clusterkraf clusterkraf = clusterkrafRef.get();
      if (clusterkraf != null) {
        ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker);
        if (clusterPoint == null) {
          if (clusterkraf.transitionsAnimation.getAnimatedDestinationClusterPoint(marker) != null) {
            exempt = true;
            // animated marker click is not supported
          } else {
            clusterPoint = clusterkraf.transitionsAnimation.getStationaryClusterPoint(marker);
          }
        }
        OnMarkerClickDownstreamListener downstreamListener = clusterkraf.options.getOnMarkerClickDownstreamListener();
        if (exempt == false && downstreamListener != null) {
          handled = downstreamListener.onMarkerClick(marker, clusterPoint);
        }
        if (exempt == false && handled == false && clusterPoint != null) {
          if (clusterPoint.size() > 1) {
            switch(clusterkraf.options.getClusterClickBehavior()) {
              case ZOOM_TO_BOUNDS:
                clusterkraf.zoomToBounds(clusterPoint);
                handled = true;
                break;
              case SHOW_INFO_WINDOW:
                clusterkraf.showInfoWindow(marker, clusterPoint);
                handled = true;
                break;
              case NO_OP:
                // no-op
                break;
            }
          } else {
            switch(clusterkraf.options.getSinglePointClickBehavior()) {
              case SHOW_INFO_WINDOW:
                clusterkraf.showInfoWindow(marker, clusterPoint);
                handled = true;
                break;
              case NO_OP:
                // no-op
                break;
            }
          }
        }
      }
      return handled || exempt;
    }

    /**
     * @see com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener#
     *      onInfoWindowClick(com.google.android.gms.maps.model.Marker)
     */
    @Override
    public void onInfoWindowClick(Marker marker) {
      Clusterkraf clusterkraf = clusterkrafRef.get();
      if (clusterkraf != null) {
        boolean handled = false;
        ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker);
        OnInfoWindowClickDownstreamListener downstreamListener = clusterkraf.options.getOnInfoWindowClickDownstreamListener();
        if (downstreamListener != null) {
          handled = downstreamListener.onInfoWindowClick(marker, clusterPoint);
        }
        if (handled == false && clusterPoint != null) {
          if (clusterPoint.size() > 1) {
            switch(clusterkraf.options.getClusterInfoWindowClickBehavior()) {
              case ZOOM_TO_BOUNDS:
                clusterkraf.zoomToBounds(clusterPoint);
                break;
              case HIDE_INFO_WINDOW:
                marker.hideInfoWindow();
                break;
              case NO_OP:
                // no-op
                break;
            }
          } else {
            switch(clusterkraf.options.getSinglePointInfoWindowClickBehavior()) {
              case HIDE_INFO_WINDOW:
                marker.hideInfoWindow();
                break;
              case NO_OP:
                // no-op
                break;
            }
          }
        }

      }

    }

        /**
         * @see com.google.android.gms.maps.GoogleMap.InfoWindowAdapter
         *      getInfoWindow(com.google.android.gms.maps.model.Marker)
         */
        @Override
        public View getInfoWindow(Marker marker) {
            View infoWindow = null;
            Clusterkraf clusterkraf = clusterkrafRef.get();
            if (clusterkraf != null) {
                ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker);
                InfoWindowDownstreamAdapter infoWindowDownstreamAdapter = clusterkraf.options.getInfoWindowDownstreamAdapter();
                if (infoWindowDownstreamAdapter != null) {
                    infoWindow = infoWindowDownstreamAdapter.getInfoWindow(marker, clusterPoint);
                }
            }
            return infoWindow; // Google Map will handle it when null
        }

        /**
         * @see com.google.android.gms.maps.GoogleMap.InfoWindowAdapter
         *      getInfoContents(com.google.android.gms.maps.model.Marker)
         */
        @Override
        public View getInfoContents(Marker marker) {
            View infoWindow = null;
            Clusterkraf clusterkraf = clusterkrafRef.get();
            if (clusterkraf != null) {
                ClusterPoint clusterPoint = clusterkraf.currentClusterPointsByMarker.get(marker);
                InfoWindowDownstreamAdapter infoWindowDownstreamAdapter = clusterkraf.options.getInfoWindowDownstreamAdapter();
                if (infoWindowDownstreamAdapter != null) {
                    infoWindow = infoWindowDownstreamAdapter.getInfoContents(marker, clusterPoint);
                }
            }
            return infoWindow; // Google Map will handle it when null
        }
    }

  abstract private class BaseClusteringTaskHost implements ClusteringTask.Host {

    private ClusteringTask task;

    BaseClusteringTaskHost() {
      this.task = new ClusteringTask(this);
    }

    @Override
    public void onClusteringTaskPostExecute(ClusteringTask.Result result) {
      currentClusters = result.currentClusters;
      onCurrentClustersSet(result);
      task = null;
    }

    public void cancel() {
      ProcessingListener processingListener = options.getProcessingListener();
      if (processingListener != null) {
        processingListener.onClusteringFinished();
      }
      task.cancel(true);
      task = null;
    }

    @SuppressLint("NewApi")
    public void executeTask() {
      GoogleMap map = mapRef.get();
      if (map != null) {
        ProcessingListener processingListener = options.getProcessingListener();
        if (processingListener != null) {
          processingListener.onClusteringStarted();
        }

        ClusteringTask.Argument arg = new ClusteringTask.Argument();
        arg.projection = map.getProjection();
        arg.options = options;
        arg.points = points;
        arg.previousClusters = previousClusters;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
          task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arg);
        } else {
          task.execute(arg);
        }
      }

    }

    abstract protected void onCurrentClustersSet(ClusteringTask.Result result);

  }

  private class ShowAllClustersClusteringTaskHost extends BaseClusteringTaskHost {

    @Override
    protected void onCurrentClustersSet(ClusteringTask.Result result) {
      ProcessingListener processingListener = options.getProcessingListener();
      if (processingListener != null) {
        processingListener.onClusteringFinished();
      }
      showAllClusters();
    }
  }

  private class UpdateClustersAndTransitionClusteringTaskHost extends BaseClusteringTaskHost {

    @Override
    protected void onCurrentClustersSet(ClusteringTask.Result result) {
      startClusterTransitionsBuildingTask(result.projection);
    }
  }

  private class ClusterTransitionsBuildingTaskHost implements ClusterTransitionsBuildingTask.Host {

    private ClusterTransitionsBuildingTask task;

    ClusterTransitionsBuildingTaskHost() {
      this.task = new ClusterTransitionsBuildingTask(this);
    }

    @Override
    public void onClusterTransitionsBuildingTaskPostExecute(ClusterTransitionsBuildingTask.Result result) {
      ProcessingListener processingListener = options.getProcessingListener();
      if (processingListener != null) {
        processingListener.onClusteringFinished();
      }
      if (result != null) {
        transitionClusters(result.clusterTransitions);
      }
      task = null;
    }

    public void cancel() {
      task.cancel(true);
      task = null;
    }

    @SuppressLint("NewApi")
    public void executeTask(Projection projection) {
      if (projection != null) {
        ClusterTransitionsBuildingTask.Argument arg = new ClusterTransitionsBuildingTask.Argument();
        arg.currentClusters = currentClusters;
        arg.previousClusters = previousClusters;
        arg.projection = projection;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
          task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, arg);
        } else {
          task.execute(arg);
        }
      }
    }

  }

  public interface ProcessingListener {
    void onClusteringStarted();

    void onClusteringFinished();
  }

}




Java Source Code List

android.UnusedStub.java
com.twotoasters.clusterkraf.AnimatedTransition.java
com.twotoasters.clusterkraf.BasePoint.java
com.twotoasters.clusterkraf.ClusterPoint.java
com.twotoasters.clusterkraf.ClusterTransitionsAnimation.java
com.twotoasters.clusterkraf.ClusterTransitionsBuildingTask.java
com.twotoasters.clusterkraf.ClusterTransitions.java
com.twotoasters.clusterkraf.ClusteringOnCameraChangeListener.java
com.twotoasters.clusterkraf.ClusteringTask.java
com.twotoasters.clusterkraf.Clusterkraf.java
com.twotoasters.clusterkraf.ClustersBuilder.java
com.twotoasters.clusterkraf.InfoWindowDownstreamAdapter.java
com.twotoasters.clusterkraf.InputPoint.java
com.twotoasters.clusterkraf.MarkerOptionsChooser.java
com.twotoasters.clusterkraf.OnInfoWindowClickDownstreamListener.java
com.twotoasters.clusterkraf.OnMarkerClickDownstreamListener.java
com.twotoasters.clusterkraf.Options.java
com.twotoasters.clusterkraf.sample.AdvancedModeFragment.java
com.twotoasters.clusterkraf.sample.DelayedIndeterminateProgressBarRunnable.java
com.twotoasters.clusterkraf.sample.MainActivity.java
com.twotoasters.clusterkraf.sample.MarkerData.java
com.twotoasters.clusterkraf.sample.NormalModeFragment.java
com.twotoasters.clusterkraf.sample.RandomPointsProvider.java
com.twotoasters.clusterkraf.sample.SampleActivity.java
com.twotoasters.clusterkraf.sample.SingleChoiceDialogFragment.java
com.twotoasters.clusterkraf.sample.ToastedMarkerOptionsChooser.java
com.twotoasters.clusterkraf.sample.ToastedOnMarkerClickDownstreamListener.java
com.twotoasters.clusterkraf.sample.TwoToastersActivity.java
com.twotoasters.clusterkraf.util.Distance.java