/***
* FINMap.java by Eric Hare
* This class handles the MapView activity of our application
* It includes user location detection and overlay support
* The Google Maps API is used to accomplish these tasks
*/
package com.net.finditnow;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.HashMap;
import java.util.List;
import org.json.JSONArray;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
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.Overlay;
import com.google.android.maps.OverlayItem;
// DESIGN PATTERN: Sub-Classing. This module extends the MapActivity class to implement a map view
// It adds some non-standard functionality, including location and item overlays
public class FINMap extends MapActivity {
// Map and Location Variables
private MapView mapView;
private MapController mapController;
private MyLocationOverlay locOverlay;
// Overlay Variables
private List<Overlay> mapOverlays;
private Drawable drawable;
private UWOverlay itemizedOverlay;
// Shared static variables that the other modules can access
private String category;
private String itemName;
// Location and GeoPoint Variables
// DESIGN PATTERN: Encapsulation. Location is sensitive information, and thus private
// But can be accessed via getLocation()
private static GeoPoint location;
private HashMap<GeoPoint, CategoryItem> geoPointItem;
// A constant representing the default location of the user
// Change this the coordinates of another campus if desired (defaults to UW Seattle)
public static final GeoPoint DEFAULT_LOCATION = new GeoPoint(47654799,-122307776);
/**
* Called when the activity is first created.
* It initializes the map layout, detects the user's category, and builds the map
*/
@Override
public void onCreate(Bundle savedInstanceState) {
// Restore the saved instance and generate the primary (main) layout
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
// Get the item name for buildings, supplies:
Bundle extras = getIntent().getExtras();
category = extras.getString("category");
itemName = extras.getString("itemName");
// Set the Breadcrumb in the titlebar
if (itemName == null) {
setTitle("FindItNow > " + FINUtil.capFirstChar(category));
} else {
setTitle("FindItNow > " + FINUtil.capFirstChar(category) + " > " + FINUtil.capFirstChar(itemName));
}
// Retrieve locations from the database and parse them
JSONArray listOfLocations = Get.requestFromDB(category, FINUtil.deCapFirstChar(FINUtil.depluralize(itemName)), DEFAULT_LOCATION);
if (listOfLocations == null) {
geoPointItem = JsonParser.parseCategoryJson("");
} else {
geoPointItem = JsonParser.parseCategoryJson(listOfLocations.toString());
}
// Create the map and the map view and detect user location
createMap();
locateUser();
// Add these locations to the map view
placeOverlays();
}
/**
* Called when the activity is paused to disable the location services
*/
@Override
public void onPause() {
super.onPause();
locOverlay.disableMyLocation();
FINSplash.lastLocation = location;
FINSplash.mapCenter = mapView.getMapCenter();
FINSplash.zoomLevel = mapView.getZoomLevel();
mapOverlays.remove(locOverlay);
}
/**
* Called when the activity is resumed to enable the location services
*/
@Override
public void onResume() {
super.onResume();
locOverlay.enableMyLocation();
location = FINSplash.lastLocation;
mapController.setCenter(FINSplash.mapCenter);
mapController.setZoom(FINSplash.zoomLevel);
mapOverlays.add(locOverlay);
}
/**
* This method returns the distance from the user's current location to a given point
* It returns this in BigDecimal format for ease of processing
*
* @param point A GeoPoint corresponding to the location under consideration
*
* @return A BigDecimal representing the distance to this point in miles
*/
public static BigDecimal distanceBetween(GeoPoint point1, GeoPoint point2) {
// Define a math context and two location variables to process
MathContext mc = new MathContext(2);
Location loc1 = new Location("");
Location loc2 = new Location("");
// This method is valid so long as the location is not the default and not null
if (point1 != null && point2 != null) {
// Compute the latitude and longitude of the two points
// Add these values to our location variable
loc1.setLatitude((float)(point1.getLatitudeE6()*1E-6));
loc1.setLongitude((float)(point1.getLongitudeE6()*1E-6));
loc2.setLatitude((float)(point2.getLatitudeE6()*1E-6));
loc2.setLongitude((float)(point2.getLongitudeE6()*1E-6));
// Return this value in miles rounded
return new BigDecimal(loc1.distanceTo(loc2) * 0.000621371192, mc);
} else {
// Return -1 if the location was not valid
return new BigDecimal(-1);
}
}
/**
* This method returns the category selected by the user
* @return A String representing the category chosen
*/
public String getCategory() {
return category;
}
/**
* This method returns an item located at the point p
* @param p A GeoPoint representing the location to retrieve the category item
* @return A CategoryItem object containing the list of locations
*/
public CategoryItem getCategoryItem(GeoPoint p){
return geoPointItem.get(p);
}
/**
* This method returns the item name selected by the user if supplies is chosen
* @return A String representing the item name, null if supplies is not chosen
*/
public String getItemName() {
return itemName;
}
/**
* This method returns the user's current location
* @return GeoPoint representing the user's location
*/
public static GeoPoint getLocation() {
return location;
}
/**
* This method computes the walking time for a given distance based on the mile time
*
* @param distance The distance to calculate walking time over
* @param mile_time The amount of time in minutes to walk a mile
*
* @return The walking time in minutes that it takes to walk the given distance rounded
*/
public static int walkingTime(BigDecimal distance, int mile_time) {
BigDecimal dec = new BigDecimal(mile_time * distance.doubleValue(), new MathContext(2));
return dec.intValue();
}
/**
* This method creates the map and displays the overlays on top of it
*/
private void createMap() {
// Initialize our MapView and MapController
mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
mapController = mapView.getController();
// Build up our overlays and initialize our "UWOverlay" class
mapOverlays = mapView.getOverlays();
drawable = this.getResources().getDrawable(FINMenu.getIcon(getCategory()));
itemizedOverlay = new UWOverlay(drawable, this, category, itemName, geoPointItem);
}
/**
* Required for Android Maps API compatibility
*/
@Override
protected boolean isRouteDisplayed() {
return false;
}
/**
* This method locates the user and displays the user's location in an overlay icon
*/
private void locateUser() {
// Define a new LocationOverlay and enable it
locOverlay = new MyLocationOverlay(this, mapView) {
// Extend onLocationChanged() to add the result to the location variable
@Override
public void onLocationChanged(Location loc) {
super.onLocationChanged(loc);
location = new GeoPoint((int)(loc.getLatitude()*1E6), (int)(loc.getLongitude()*1E6));
}
};
// Define an Android Runnable
Runnable runnable = new Runnable() {
// Run this method with a fix on location has been received
public void run() {
// Only update to new location if user has not moved the map
if (mapView.getMapCenter().equals(FINSplash.mapCenter))
mapController.animateTo(locOverlay.getMyLocation());
}
};
// In this case, we have cleanly started the app and should fix on user location
if (!category.equals("buildings") && FINSplash.lastLocation == null) {
Toast.makeText(this, "Getting a fix on your location...", Toast.LENGTH_SHORT).show();
locOverlay.runOnFirstFix(runnable);
}
}
/**
* Create the Android options menu
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
/**
* Expand and define the Android options menu
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle the item selected
switch (item.getItemId()) {
// Return to the categories screen
case R.id.categories_button:
startActivity(new Intent(this, FINMenu.class));
return true;
// Center the map on the user's location if it is possible
case R.id.my_location_button:
if (location != null) {
mapController.animateTo(location);
} else {
Toast.makeText(this, "Error: Could not detect your location", Toast.LENGTH_SHORT).show();
}
return true;
// Add a new location to the map
case R.id.add_new_button:
startActivity(new Intent(this, FINAddNew.class));
return true;
// Open up our help documentation
case R.id.help_button:
startActivity(new Intent(this, FINHelp.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* This method places the locations retrieved from the database onto the map
*/
private void placeOverlays() {
// If the category is buildings, then we only put the single point on the map
if (getCategory().equals("buildings")) {
GeoPoint point = FINMenu.getGeoPointFromBuilding(itemName);
CategoryItem item = new CategoryItem();
for (String flr : FINMenu.getBuilding(point).getFloorNames()) {
item.addFloor_names(flr);
}
geoPointItem.put(point, item);
mapController.animateTo(point);
}
// Loop over the locations that we have retrieved and add them
// DESIGN PATTERN: Iteration. We iterate over the set of GeoPoints corresponding to items
// We designed geoPointItem to allow for simple iteration for this purpose
for (GeoPoint point : geoPointItem.keySet()) {
OverlayItem overlayItem = new OverlayItem(point, "", "");
itemizedOverlay.addOverlay(overlayItem);
}
// If we have retrieved items to display, add them to the overlay list
if (!geoPointItem.keySet().isEmpty()) {
mapOverlays.add(itemizedOverlay);
}
}
}
|