/**
* Copyright 2009 Joe LaPenna
*/
package com.joelapenna.foursquared;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.OverlayItem;
import com.joelapenna.foursquare.types.Checkin;
import com.joelapenna.foursquare.types.Group;
import com.joelapenna.foursquare.types.Venue;
import com.joelapenna.foursquare.util.VenueUtils;
import com.joelapenna.foursquared.maps.CheckinGroup;
import com.joelapenna.foursquared.maps.CheckinGroupItemizedOverlay;
import com.joelapenna.foursquared.maps.CrashFixMyLocationOverlay;
import com.joelapenna.foursquared.maps.CheckinGroupItemizedOverlay.CheckinGroupOverlayTapListener;
import com.joelapenna.foursquared.util.CheckinTimestampSort;
import com.joelapenna.foursquared.util.GeoUtils;
import com.joelapenna.foursquared.widget.MapCalloutView;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
/**
* @author Joe LaPenna (joe@joelapenna.com)
* @author Mark Wyszomierski (markww@gmail.com)
* -Added support for checkingroup items, also stopped recreation
* of overlay group in onResume(). [2010-06-21]
*/
public class FriendsMapActivity extends MapActivity {
public static final String TAG = "FriendsMapActivity";
public static final boolean DEBUG = FoursquaredSettings.DEBUG;
private String mTappedVenueId;
private Observer mSearchResultsObserver;
private MapCalloutView mCallout;
private MapView mMapView;
private MapController mMapController;
private ArrayList<CheckinGroupItemizedOverlay> mCheckinsGroupOverlays =
new ArrayList<CheckinGroupItemizedOverlay>();
private MyLocationOverlay mMyLocationOverlay;
private boolean mConstructedPinsOnce;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_map_activity);
mConstructedPinsOnce = false;
mCallout = (MapCalloutView) findViewById(R.id.map_callout);
mCallout.setVisibility(View.GONE);
mCallout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(FriendsMapActivity.this, VenueActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Foursquared.EXTRA_VENUE_ID, mTappedVenueId);
startActivity(intent);
}
});
initMap();
mSearchResultsObserver = new Observer() {
@Override
public void update(Observable observable, Object data) {
if (DEBUG) Log.d(TAG, "Observed search results change.");
clearMap();
loadSearchResults(FriendsActivity.searchResultsObservable.getSearchResults());
recenterMap();
}
};
}
@Override
public void onResume() {
super.onResume();
if (DEBUG) Log.d(TAG, "onResume()");
mMyLocationOverlay.enableMyLocation();
//mMyLocationOverlay.enableCompass(); // Disabled due to a sdk 1.5
// emulator bug
// If this is the first time we're showing the activity, try generating the overlay
// immediately. If the results are already loaded, the observer would not be called.
if (!mConstructedPinsOnce) {
clearMap();
loadSearchResults(FriendsActivity.searchResultsObservable.getSearchResults());
recenterMap();
mConstructedPinsOnce = true;
}
FriendsActivity.searchResultsObservable.addObserver(mSearchResultsObserver);
}
@Override
public void onPause() {
super.onPause();
if (DEBUG) Log.d(TAG, "onPause()");
mMyLocationOverlay.disableMyLocation();
mMyLocationOverlay.disableCompass();
FriendsActivity.searchResultsObservable.deleteObserver(mSearchResultsObserver);
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
private void initMap() {
mMapView = (MapView) findViewById(R.id.mapView);
mMapView.setBuiltInZoomControls(true);
mMapController = mMapView.getController();
mMyLocationOverlay = new CrashFixMyLocationOverlay(this, mMapView);
mMapView.getOverlays().add(mMyLocationOverlay);
}
private void loadSearchResults(Group<Checkin> checkins) {
if (checkins == null) {
if (DEBUG) Log.d(TAG, "no search results. Not loading.");
return;
}
if (DEBUG) Log.d(TAG, "Loading search results");
// One CheckinItemizedOverlay per group!
CheckinGroupItemizedOverlay mappableCheckinsOverlay = createMappableCheckinsOverlay(checkins);
if (mappableCheckinsOverlay != null) {
if (DEBUG) Log.d(TAG, "adding a map view checkin overlay.");
mCheckinsGroupOverlays.add(mappableCheckinsOverlay);
}
// Only add the list of checkin group overlays if it contains any
// overlays.
if (mCheckinsGroupOverlays.size() > 0) {
mMapView.getOverlays().addAll(mCheckinsGroupOverlays);
} else {
Toast.makeText(this, getResources().getString(
R.string.friendsmapactivity_no_checkins), Toast.LENGTH_LONG).show();
}
}
private void clearMap() {
if (DEBUG) Log.d(TAG, "clearMap()");
mCheckinsGroupOverlays.clear();
mMapView.getOverlays().clear();
mMapView.getOverlays().add(mMyLocationOverlay);
mMapView.postInvalidate();
}
/**
* Create an overlay that contains a specific group's list of mappable
* checkins.
*/
private CheckinGroupItemizedOverlay createMappableCheckinsOverlay(Group<Checkin> group) {
// We want to group checkins by venue. Do max three checkins per venue, a total
// of 100 venues total. We should only also display checkins that are within a
// city radius, and are at most three hours old.
CheckinTimestampSort timestamps = new CheckinTimestampSort();
Map<String, CheckinGroup> checkinMap = new HashMap<String, CheckinGroup>();
for (int i = 0, m = group.size(); i < m; i++) {
Checkin checkin = (Checkin)group.get(i);
Venue venue = checkin.getVenue();
if (VenueUtils.hasValidLocation(venue)) {
// Make sure the venue is within city radius.
try {
int distance = Integer.parseInt(checkin.getDistance());
if (distance > FriendsActivity.CITY_RADIUS_IN_METERS) {
continue;
}
} catch (NumberFormatException ex) {
// Distance was invalid, ignore this checkin.
continue;
}
// Make sure the checkin happened within the last three hours.
try {
Date date = new Date(checkin.getCreated());
if (date.before(timestamps.getBoundaryRecent())) {
continue;
}
} catch (Exception ex) {
// Timestamps was invalid, ignore this checkin.
continue;
}
String venueId = venue.getId();
CheckinGroup cg = checkinMap.get(venueId);
if (cg == null) {
cg = new CheckinGroup();
checkinMap.put(venueId, cg);
}
// Stop appending if we already have three checkins here.
if (cg.getCheckinCount() < 3) {
cg.appendCheckin(checkin);
}
}
// We can't have too many pins on the map.
if (checkinMap.size() > 99) {
break;
}
}
Group<CheckinGroup> mappableCheckins = new Group<CheckinGroup>(checkinMap.values());
if (mappableCheckins.size() > 0) {
CheckinGroupItemizedOverlay mappableCheckinsGroupOverlay = new CheckinGroupItemizedOverlay(
this,
((Foursquared) getApplication()).getRemoteResourceManager(),
this.getResources().getDrawable(R.drawable.pin_checkin_multiple),
mCheckingGroupOverlayTapListener);
mappableCheckinsGroupOverlay.setGroup(mappableCheckins);
return mappableCheckinsGroupOverlay;
} else {
return null;
}
}
private void recenterMap() {
// Previously we'd try to zoom to span, but this gives us odd results a lot of times,
// so falling back to zoom at a fixed level.
GeoPoint center = mMyLocationOverlay.getMyLocation();
if (center != null) {
if (DEBUG) Log.d(TAG, "Using MyLocaionOverlay as center point for recenterMap().");
mMapController.animateTo(center);
mMapController.setZoom(16);
} else {
// Location overlay wasn't ready yet, try using last known geolocation from manager.
Location bestLocation = GeoUtils.getBestLastGeolocation(this);
if (bestLocation != null) {
if (DEBUG) Log.d(TAG, "Using last known location as center point for recenterMap().");
mMapController.animateTo(GeoUtils.locationToGeoPoint(bestLocation));
mMapController.setZoom(16);
} else {
// We have no location information at all, so we'll just show the map at a high
// zoom level and the user can zoom in as they wish.
if (DEBUG) Log.d(TAG, "No location info available for recenterMap().");
mMapController.setZoom(8);
}
}
}
/** Handle taps on one of the pins. */
private CheckinGroupOverlayTapListener mCheckingGroupOverlayTapListener =
new CheckinGroupOverlayTapListener() {
@Override
public void onTap(OverlayItem itemSelected, OverlayItem itemLastSelected, CheckinGroup cg) {
mTappedVenueId = cg.getVenueId();
mCallout.setTitle(cg.getVenueName());
mCallout.setMessage(cg.getDescription());
mCallout.setVisibility(View.VISIBLE);
mMapController.animateTo(new GeoPoint(cg.getLatE6(), cg.getLonE6()));
}
@Override
public void onTap(GeoPoint p, MapView mapView) {
mCallout.setVisibility(View.GONE);
}
};
}