package org.mips.Sirius.ui;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import org.mips.R;
import org.mips.Sirius.components.Line;
import org.mips.Sirius.components.StopMonitoring;
import org.mips.Sirius.components.StopPoint;
import org.mips.Sirius.components.Vehicle;
import org.mips.Sirius.provider.ProviderException;
import org.mips.Sirius.provider.TransportInformationProvider;
import org.mips.Sirius.provider.SIRI.SIRIProvider;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
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;
/*
* Main class
* Implements the user interface and binds it with the needed components
*/
public class SiriusMap extends MapActivity implements LocationListener {
/*
* Private fields
*/
// user interface
private MapView map;
private MapController mapController;
public static final int DIALOG_FATAL_ERROR_ID = 1;
public static final int DIALOG_NETWORK_ERROR_ID = 2;
public static final int DIALOG_GPS_ERROR_ID = 3;
public static final int DIALOG_LOADING = 4;
public static final String MENU_FIND_ME = "Find me";
public static final String MENU_TOGGLE_MAP = "Toggle satellite";
private GeoPoint lastKnownLocation = null;
private HashMap<String, Integer> linesColouring;
private SiriusLoadingDialog loadingDialog;
private SiriusLoadingDialog tapLoadingDialog;
private SiriusStopMonitoringDialog smDialog;
// components
private LocationManager locationManager;
private TransportInformationProvider provider;
// default configuration
private final int DEFAULT_ZOOM_LEVEL = 15;
private final String providerURL = "http://mips42.altervista.org/etc/siri_feed.php";
private final int requestDelay = 5000;
// constants
private final String LINES_DRAWN = "lines_drown";
private final String STOPPOINTS_DRAWN = "stoppoints_drown";
private final String VEHICLES_DRAWN = "vehicles_drown";
/*
* Constructor
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
linesColouring = new HashMap<String, Integer>();
// create map
map = createMap();
if (map == null) {
Log.d("debug", "creating map failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
// set up a default transport information source
// FIXME: don't hardcode
try {
provider = new SIRIProvider(new URL(providerURL), requestDelay);
} catch (MalformedURLException e) {
Log.d("debug", "malformed URLs in config");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
if (provider == null) {
Log.d("debug", "creating information provider failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
// create a location manager, a GPS listener and bind them
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager == null) {
Log.d("debug", "creating location manager failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0,
0, this);
// add an overlay showing where the user is
MyLocationOverlay userLocationOverlay = new FixedMyLocationOverlay(
this, map);
userLocationOverlay.enableMyLocation();
userLocationOverlay.enableCompass();
map.getOverlays().add(userLocationOverlay);
Drawable defaultMarker;
try {
defaultMarker = MarkerFactory.createDefaultMarker();
} catch (SiriusUIException e) {
Log.d("debug", "no internet connectivity");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
// add an overlay showing the transit map (lines)
LinesMapOverlay linesOverlay = new LinesMapOverlay(defaultMarker);
if (linesOverlay == null) {
Log.d("debug", "creating lines overlay failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
map.getOverlays().add(linesOverlay); // place it on top of the map
// add an overlay showing where buses are
VehicleMapOverlay busOverlay = new VehicleMapOverlay(defaultMarker);
if (busOverlay == null) {
Log.d("debug", "creating bus overlay failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
map.getOverlays().add(busOverlay); // place it on top of the map
// add an overlay showing where stops are
StopPointsMapOverlay stopOverlay = new StopPointsMapOverlay(
defaultMarker);
if (stopOverlay == null) {
Log.d("debug", "creating stop overlay failed");
showDialog(DIALOG_FATAL_ERROR_ID);
return;
}
map.getOverlays().add(stopOverlay); // place it on top of the map
// set map as current view
setContentView(map);
// prepare dialogs
ArrayList<String> tokens = new ArrayList<String>();
tokens.add(LINES_DRAWN);
tokens.add(STOPPOINTS_DRAWN);
tokens.add(VEHICLES_DRAWN);
loadingDialog = new SiriusLoadingDialog(this, tokens);
smDialog = new SiriusStopMonitoringDialog(this);
loadingDialog.show();
}
/*
* Creates the map, i.e. the main user interface
*/
private MapView createMap() {
// FIXME: generate "release" API key
MapView map = new MapView(this, getString(R.string.maps_api_key));
mapController = map.getController();
mapController.setZoom(DEFAULT_ZOOM_LEVEL); // set a default zoom level
map.setSatellite(true);
map.setClickable(true);
map.setBuiltInZoomControls(true); // allow user to change zoom level
return map;
}
/*
* Called the first time a certain dialog is called
*/
@Override
protected Dialog onCreateDialog(int dialogId) {
Dialog dialog;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(false);
switch (dialogId) {
case DIALOG_FATAL_ERROR_ID:
builder.setMessage("Sorry, I couldn't boot and I'm dying.");
builder.setNeutralButton("Ok",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
});
dialog = builder.create();
break;
case DIALOG_NETWORK_ERROR_ID:
builder
.setMessage("A network error has occured. Please try again.");
builder.setNeutralButton("Ok",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
dialog = builder.create();
break;
case DIALOG_GPS_ERROR_ID:
builder.setMessage("Your current location is not yet available");
builder.setNeutralButton("Ok",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
dialog = builder.create();
break;
case DIALOG_LOADING:
SiriusLoadingDialog sldialog = new SiriusLoadingDialog(this);
sldialog.setMessage("Loading");
dialog = sldialog;
break;
default:
dialog = null;
Log.d("debug", "unknown dialog requested");
break;
}
if (dialog == null) {
Log.d("debug", "building alert dialog failed");
}
return dialog;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(MENU_FIND_ME);
menu.add(MENU_TOGGLE_MAP);
return true;
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (item.getTitle() == MENU_FIND_ME) {
if (lastKnownLocation == null) {
showDialog(DIALOG_GPS_ERROR_ID);
} else {
map.getController().setCenter(lastKnownLocation);
}
} else if (item.getTitle() == MENU_TOGGLE_MAP) {
map.setSatellite(!map.isSatellite());
}
return true;
}
/*
* Tell the Maps API we're not using route information (yet)
*/
@Override
protected boolean isRouteDisplayed() {
return false;
}
private class VehicleMapOverlay extends SiriusOverlay {
public VehicleMapOverlay(Drawable defaultMarker) {
super(defaultMarker);
this.populate();
}
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
long when) {
super.draw(canvas, mapView, shadow);
synchronized (provider) {
ArrayList<Vehicle> vehicles;
try {
vehicles = provider.getRealtimeVehicleInformation();
} catch (ProviderException pe) {
Log.d("debug",
"Failed to retrieve real-time vehicle information"
+ pe.getCause().getMessage());
return true;
}
if ((vehicles == null) || linesColouring.isEmpty())
return true;
this.items = new ArrayList<OverlayItem>();
// draw markers for each vehicle
for (Vehicle vehicle : vehicles) {
OverlayItem item = new OverlayItem(
vehicle.getCoordinates(), "", "");
// get a marker for the vehicle
Drawable marker;
try {
marker = MarkerFactory.createVehicleMarker(vehicle
.getName(), Colouring
.getHTMLColour(linesColouring.get(vehicle
.getLineReference())));
} catch (SiriusUIException e) {
Log.d("debug", "no internet connectivity");
showDialog(DIALOG_FATAL_ERROR_ID);
return false;
}
ItemizedOverlay.boundCenterBottom(marker);
item.setMarker(marker);
this.items.add(item);
}
this.populate();
loadingDialog.notifyReady(VEHICLES_DRAWN);
return true;
}
}
}
private class StopPointsMapOverlay extends SiriusOverlay {
public StopPointsMapOverlay(Drawable defaultMarker) {
super(defaultMarker);
this.populate();
}
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
long when) {
super.draw(canvas, mapView, shadow);
synchronized (provider) {
ArrayList<StopPoint> stopPoints;
try {
stopPoints = provider.getStaticStopPointsInformation();
} catch (ProviderException pe) {
Log.d("debug", "Failed to retrieve stops information");
return true;
}
if (stopPoints == null)
return true;
this.items = new ArrayList<OverlayItem>();
// draw markers for each stop
for (StopPoint stop : stopPoints) {
OverlayItem item = new SiriusOverlayItem(stop, stop
.getCoordinates(), stop.getName(), stop
.getReference());
// get a marker for the stop
Drawable marker;
try {
marker = MarkerFactory.createStopMarker(stop
.getReference());
} catch (SiriusUIException e) {
Log.d("debug", "no internet connectivity");
showDialog(DIALOG_FATAL_ERROR_ID);
return false;
}
ItemizedOverlay.boundCenterBottom(marker);
item.setMarker(marker);
this.items.add(item);
}
this.populate();
loadingDialog.notifyReady(STOPPOINTS_DRAWN);
return false;
}
}
@Override
public boolean onTap(final int x) {
SiriusOverlayItem tappedItem = (SiriusOverlayItem) this
.createItem(x);
if (tappedItem == null) {
Log.d("tap", "Tapped item with index " + x + " not found");
return false;
}
Log.d("tap", "Tapped item with index " + x + ": "
+ tappedItem.getTitle());
tapLoadingDialog = new SiriusLoadingDialog(SiriusMap.this);
tapLoadingDialog.show();
handler.postDelayed(new Runnable() {
@Override
public void run() {
SiriusOverlayItem tappedItem = (SiriusOverlayItem) StopPointsMapOverlay.this
.createItem(x);
smDialog.setDismissable(tapLoadingDialog);
try {
StopMonitoring stopMonitoring = provider
.getRealtimeStopPointInformation(tappedItem
.getStop());
tappedItem.getStop().setLastMonitoring(stopMonitoring);
smDialog.setStop(tappedItem.getStop());
} catch (ProviderException pe) {
Log.d("tap",
"Failed to retrieve stop monitoring information for stop "
+ tappedItem.getStop());
smDialog.setStop(null);
handler.sendMessage(Message.obtain(handler, 0,
DIALOG_NETWORK_ERROR_ID, 0));
}
}
}, 500);
return true;
}
}
private class LinesMapOverlay extends SiriusOverlay {
public LinesMapOverlay(Drawable defaultMarker) {
super(defaultMarker);
this.populate();
}
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
long when) {
super.draw(canvas, mapView, shadow);
synchronized (provider) {
ArrayList<Line> lines;
try {
lines = provider.getStaticLinesInformation();
} catch (ProviderException pe) {
Log.d("debug", "Failed to retrieve lines information");
return true;
}
if (lines == null)
return true;
this.items = new ArrayList<OverlayItem>();
for (Line line : lines) {
linesColouring.put(line.getReference(), new Integer(
Colouring.getColour(lines.indexOf(line), lines
.size())));
Paint paint = new Paint();
paint.setColor(linesColouring.get(line.getReference()));
paint.setAlpha(80);
paint.setStrokeWidth(5f);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
Path path = new Path();
Point oldPoint = null;
// draw the line map
for (GeoPoint wayPoint : line.getStopPointsPath()) {
Point point = new Point();
mapView.getProjection().toPixels(wayPoint, point);
if (oldPoint != null) {
path.lineTo(point.x, point.y);
oldPoint = point;
} else {
path.moveTo(point.x, point.y);
oldPoint = point;
}
}
canvas.drawPath(path, paint);
}
loadingDialog.notifyReady(LINES_DRAWN);
return false;
}
}
}
/*
* Called whenever the GPS position of the user changes
*/
public synchronized void onLocationChanged(Location newLocation) {
Log.d("debug", "Location is now: " + newLocation.getLatitude() + " "
+ newLocation.getLongitude());
int newLatitude = (int) (newLocation.getLatitude() * 1000000);
int newLongitude = (int) (newLocation.getLongitude() * 1000000);
GeoPoint newLocationPoint = new GeoPoint(newLatitude, newLongitude);
if (newLocationPoint != lastKnownLocation) {
lastKnownLocation = newLocationPoint;
Log.d("debug", "Centering on: " + newLocationPoint.getLatitudeE6()
+ " " + newLocationPoint.getLongitudeE6());
}
}
@Override
public void onProviderDisabled(String arg0) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("tap", "handler " + msg.obj);
SiriusMap.this.showDialog(msg.arg1);
}
};
}
|