/*******************************************************************************
* Copyright (c) 2011 Michel DAVID mimah35-at-gmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package fr.gotorennes;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.location.Address;
import android.location.Geocoder;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ZoomControls;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;
import com.google.android.gms.ads.MobileAds;
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.Overlay;
import com.google.android.maps.OverlayItem;
import fr.gotorennes.ItineraireMapActivity.RouteOverlay;
import fr.gotorennes.domain.Arret;
import fr.gotorennes.domain.BikeStation;
import fr.gotorennes.domain.MetroStation;
import fr.gotorennes.domain.Station;
import fr.gotorennes.remote.RemoteService;
import fr.gotorennes.util.BackgroundTask;
import fr.gotorennes.util.LocationUtils;
import fr.gotorennes.view.MapDrawable;
public abstract class AbstractMapActivity extends MapActivity {
public static final double deg2rad = Math.PI / 180;
public static final float erad = 6371.0f;
protected MapView mapView;
protected MapController mapController;
protected ZoomControls zoomControls;
protected MyLocationOverlay myLocationOverlay;
protected GoToRennes goToRennes;
protected BroadcastReceiver receiver;
protected int getLayout() {
return R.layout.map;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
mapView = (MapView) findViewById(R.id.mapView);
mapController = mapView.getController();
myLocationOverlay = new MyLocationOverlay(this, mapView);
// Add ZoomControls
zoomControls = (ZoomControls) findViewById(R.id.zoomControl);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mapController.zoomIn();
onMapChange();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
mapController.zoomOut();
onMapChange();
}
});
addMyLocationOverlay();
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AbstractActivity.ACTION_LOGOUT);
registerReceiver(receiver, intentFilter);
BackgroundTask<Boolean> backgroundInit = new BackgroundTask<Boolean>() {
@Override
protected Boolean execute() {
try {
goToRennes = GoToRennes
.getInstance(AbstractMapActivity.this, null);
} catch (Exception ex) {
return false;
}
return true;
}
@Override
protected void callback(Boolean result) {
if (result == null || !result) {
showError(getString(R.string.erreurInitialisation), true);
} else {
init();
goToRennes.track(getTrackingName());
}
}
};
backgroundInit.start(this);
}
protected abstract String getTrackingName();
protected abstract void init();
protected void logout() {
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(AbstractActivity.ACTION_LOGOUT);
sendBroadcast(broadcastIntent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.accueil:
goToRennes.trackEvent("Menu", "Accueil");
Intent intentAccueil = new Intent(getApplicationContext(),
GoToRennesActivity.class);
intentAccueil.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intentAccueil);
return true;
case R.id.quitter:
goToRennes.trackEvent("Menu", "Quitter");
logout();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK)
loadProximityData();
}
protected abstract void loadProximityData();
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = super.dispatchTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP)
onMapChange();
return result;
}
protected void onMapChange() {
}
protected void addOverlay(Overlay overlay) {
if (!mapView.getOverlays().contains(overlay)) {
mapView.getOverlays().add(overlay);
}
}
protected void addMyLocationOverlay() {
myLocationOverlay.enableMyLocation();
addOverlay(myLocationOverlay);
}
protected void centerMap(double latitude, double longitude) {
mapController.setZoom(18);
mapController.animateTo(new GeoPoint((int) (latitude * 1e6),
(int) (longitude * 1e6)));
}
protected void addEtapeOverlay(Etape etape) {
if (etape.route == null) {
return;
}
boolean first = true;
GeoPoint lastGp = null;
for (Iterator<Location> iter = etape.route.locations.iterator(); iter
.hasNext();) {
Location location = iter.next();
GeoPoint gp = new GeoPoint((int) (location.latitude * 1E6),
(int) (location.longitude * 1E6));
if (first) {
mapView.getOverlays().add(new RouteOverlay(gp, gp, 1));
first = false;
if (etape.type != TypeEtape.BUS) {
MapItemOverlay overlay = new MapItemOverlay(
new MapDrawable(getApplicationContext(),
etape.type.icon));
overlay.addItem(new MapLocationOverlay("",
location.latitude, location.longitude, null));
mapView.getOverlays().add(overlay);
} else {
MapItemOverlay overlay = new MapItemOverlay(
new MapDrawable(getApplicationContext(),
etape.bitmapIcon, etape.bitmapCarre));
overlay.addItem(new MapLocationOverlay("",
location.latitude, location.longitude, null));
mapView.getOverlays().add(overlay);
}
} else if (!iter.hasNext()) {
mapView.getOverlays().add(new RouteOverlay(lastGp, gp, 3));
} else {
mapView.getOverlays().add(new RouteOverlay(lastGp, gp, 2));
}
lastGp = gp;
}
}
public static Location getLocation(Context context, String address) {
if ("".equals(address.trim())) {
android.location.Location l = LocationUtils.getLocation(context);
return l != null ? new Location(l.getLatitude(), l.getLongitude(),
context.getString(R.string.positionActuelle)) : null;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> addresses = geocoder.getFromLocationName(address
+ ",35 ille et vilaine", 1);
if (addresses != null && !addresses.isEmpty()) {
Address foundAddress = addresses.get(0);
return new Location(foundAddress.getLatitude(),
foundAddress.getLongitude(),
foundAddress.getAddressLine(0));
}
} catch (Exception ex) {
Log.e("GoToRennes", ex.getMessage());
}
return null;
}
protected Itineraire getItinerairePieton(int depart,
Location locationDepart, int arrivee, Location locationArrivee) {
return getItinerairePieton(depart, locationDepart, arrivee,
locationArrivee, true);
}
protected Itineraire getItinerairePieton(int depart,
Location locationDepart, int arrivee, Location locationArrivee,
boolean calculeRoute) {
Itineraire itineraire = new Itineraire();
itineraire.etapes.add(getEtape(TypeEtape.PIETON, depart,
locationDepart, arrivee, locationArrivee, calculeRoute));
return itineraire;
}
public Etape getEtape(TypeEtape type, int depart, Location locationDepart,
int arrivee, Location locationArrivee) {
return getEtape(type, depart, locationDepart, arrivee, locationArrivee,
true);
}
public Etape getEtape(TypeEtape type, int depart, Location locationDepart,
int arrivee, Location locationArrivee, boolean calculeRoute) {
Etape etape = new Etape();
etape.type = type;
etape.locationDepart = locationDepart;
etape.adresseDepart = locationDepart.adresse
+ (locationDepart.direction != null && type != TypeEtape.PIETON ? "<br/>"
+ getString(R.string.direction)
+ locationDepart.direction
: "");
etape.locationArrivee = locationArrivee;
etape.adresseArrivee = locationArrivee.adresse;
etape.depart = depart;
etape.arrivee = arrivee;
if (calculeRoute) {
etape.route = getRoute(locationDepart, locationArrivee);
}
return etape;
}
protected void calculeRoutes(Itineraire itineraire) {
for (Etape etape : itineraire.etapes) {
if (etape.type == TypeEtape.PIETON || etape.type == TypeEtape.VELO) {
etape.route = getRoute(etape.locationDepart,
etape.locationArrivee);
} else {
etape.route = getRoute(etape.locationDepart);
}
}
}
protected static Route getRoute(Location depart) {
Route route = new Route();
route.locations.add(depart);
return route;
}
/**
* Decode a polyline string into a list of GeoPoints.
*
* @param poly
* polyline encoded string to decode.
* @return the list of GeoPoints represented by this polystring.
*/
private static List<Location> decodePolyLine(final String poly) {
int len = poly.length();
int index = 0;
List<Location> decoded = new ArrayList<Location>();
int lat = 0;
int lng = 0;
while (index < len) {
int b;
int shift = 0;
int result = 0;
do {
b = poly.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = poly.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
decoded.add(new Location(lat * 1E-5, lng * 1E-5, ""));
}
return decoded;
}
protected static Route getRoute(Location depart, Location arrivee) {
final StringBuilder urlString = new StringBuilder();
urlString
.append("http://maps.google.com/maps/api/directions/json?&sensor=true&mode=walking");
urlString.append("&origin=");
urlString.append(depart.latitude);
urlString.append(",");
urlString.append(depart.longitude);
urlString.append("&destination=");
urlString.append(arrivee.latitude);
urlString.append(",");
urlString.append(arrivee.longitude);
Log.d("GoToRennes", urlString.toString());
String result = RemoteService.readFully(urlString.toString());
if (result == null) {
return null;
}
try {
JSONObject json = new JSONObject(result);
JSONObject jsonRoute = json.getJSONArray("routes").getJSONObject(0);
JSONObject polyline = jsonRoute.getJSONObject("overview_polyline");
Route route = new Route();
route.locations = decodePolyLine(polyline.getString("points"));
return route;
} catch (JSONException e) {
Log.e("GoToRennes", e.getMessage());
}
return null;
}
protected void populateItineraireDetails(Itineraire itineraire) {
LinearLayout details = (LinearLayout) findViewById(R.id.details);
for (final Etape etape : itineraire.etapes) {
addEtapeOverlay(etape);
RelativeLayout view = (RelativeLayout) getLayoutInflater().inflate(
R.layout.itineraire_listitem, null);
ImageView lineIcon = (ImageView) view.findViewById(R.id.icon);
if (etape.bitmapIcon != null) {
lineIcon.setImageBitmap(etape.bitmapIcon);
} else {
lineIcon.setImageResource(etape.type.icon);
}
TextView name = (TextView) view.findViewById(R.id.name);
name.setText(Html.fromHtml(Arret.getTime(etape.depart) + " : "
+ etape.adresseDepart + "<br />"
+ Arret.getTime(etape.arrivee) + " : "
+ etape.adresseArrivee));
TextView duree = (TextView) view.findViewById(R.id.duree);
duree.setText(etape.getDuree() + "min");
TextView distance = (TextView) view.findViewById(R.id.distance);
distance.setText(getFormattedDistance(etape.locationDepart,
etape.locationArrivee));
distance.setVisibility(etape.type == TypeEtape.PIETON
|| etape.type == TypeEtape.VELO ? View.VISIBLE : View.GONE);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Location depart = etape.locationDepart;
centerMap(depart.latitude, depart.longitude);
}
});
details.addView(view);
}
Location first = itineraire.etapes.get(0).locationDepart;
centerMap(first.latitude, first.longitude);
}
protected static class Itineraire {
public int index;
public List<Etape> etapes = new ArrayList<Etape>();
public Itineraire(Etape... etapes) {
for (Etape etape : etapes) {
this.etapes.add(etape);
}
}
public int getArrivee() {
return etapes.get(etapes.size() - 1).arrivee;
}
public String getKey() {
StringBuffer buffer = new StringBuffer();
for (Etape etape : etapes) {
buffer.append(etape.getKey());
}
return buffer.toString();
}
}
protected static enum TypeEtape {
PIETON(R.drawable.pieton), VELO(R.drawable.bike), BUS(R.drawable.bus), METRO(
R.drawable.metro);
public int icon;
private TypeEtape(int icon) {
this.icon = icon;
}
}
protected static class Etape {
public TypeEtape type;
public Bitmap bitmapIcon;
public boolean bitmapCarre;
public int depart;
public String adresseDepart;
public Location locationDepart;
public int arrivee;
public String adresseArrivee;
public Location locationArrivee;
public String ligne;
public Route route;
public int getDuree() {
return arrivee - depart;
}
public String getKey() {
switch (type) {
case VELO:
return "V";
case METRO:
return "M";
case PIETON:
return "P";
default:
return ligne;
}
}
}
protected static class Route {
List<Location> locations = new ArrayList<Location>();
}
protected static class Location {
double latitude;
double longitude;
String adresse;
String direction;
public Location(BikeStation station, Context context) {
latitude = station.latitude;
longitude = station.longitude;
adresse = context.getString(R.string.station) + " " + station.name;
}
public Location(MetroStation station, String direction, Context context) {
latitude = station.latitude;
longitude = station.longitude;
adresse = context.getString(R.string.station) + " " + station.name;
this.direction = direction;
}
public Location(Station station, String direction, Context context) {
latitude = station.latitude;
longitude = station.longitude;
adresse = context.getString(R.string.arret) + " " + station.nom;
this.direction = direction;
}
public Location(String[] lngLat) {
this(lngLat, null);
}
public Location(String[] lngLat, String adresse) {
latitude = Double.parseDouble(lngLat[1]);
longitude = Double.parseDouble(lngLat[0]);
this.adresse = adresse;
}
public Location(double latitude, double longitude, String adresse) {
this.latitude = latitude;
this.longitude = longitude;
this.adresse = adresse;
}
}
public static int getTempsMarche(Location depart, Location arrivee) {
double distance = getDistance(depart, arrivee);
return (int) (distance * 60 / 4); // 4km/h
}
public static int getTempsVelo(Location depart, Location arrivee) {
double distance = getDistance(depart, arrivee);
return (int) (distance * 60 / 11); // 11km/h
}
public static String getFormattedDistance(Location depart, Location arrivee) {
return formatDistance(getDistance(depart, arrivee));
}
public static double getDistance(Location depart, Location arrivee) {
return AbstractMapActivity.getDistance(depart.latitude,
depart.longitude, arrivee.latitude, arrivee.longitude);
}
public static String getFormattedDistance(double lat1, double lon1,
double lat2, double lon2) {
return formatDistance(getDistance(lat1, lon1, lat2, lon2));
}
public static double getDistance(double lat1, double lon1, double lat2,
double lon2) {
double dLat = (lat2 - lat1) * deg2rad;
double dLon = (lon2 - lon1) * deg2rad;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(lat1 * deg2rad) * Math.cos(lat2 * deg2rad)
* Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return c * erad;
}
public static String formatDistance(double distance) {
if (distance > 100) {
return String.format(Locale.getDefault(), "%.0f",
new Double(Math.round(distance)))
+ " km";
} else if (distance > 10) {
return String.format(Locale.getDefault(), "%.1f",
new Double(Math.round(distance * 10.0) / 10.0))
+ " km";
} else if (distance > 1) {
return String.format(Locale.getDefault(), "%.2f",
new Double(Math.round(distance * 100.0) / 100.0))
+ " km";
} else {
return String.format(Locale.getDefault(), "%.0f",
new Double(Math.round(distance * 1000.0)))
+ " m";
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
myLocationOverlay.disableMyLocation();
if (isTaskRoot()) {
if (goToRennes != null)
goToRennes.stop();
}
if (adView != null) {
adView.destroy();
}
}
@Override
protected void onPause() {
super.onPause();
myLocationOverlay.disableMyLocation();
LocationUtils.desactiveLocation(this);
}
private AdView adView = null;
@Override
protected void onResume() {
super.onResume();
myLocationOverlay.enableMyLocation();
LocationUtils.activeLocation(this);
ViewGroup layout = (ViewGroup) findViewById(R.id.adLayout);
if (layout != null && adView == null) {
MobileAds.initialize(getApplicationContext(), "ca-app-pub-5396385527314971/1328911094");
adView = new AdView(this);
adView.setAdSize(AdSize.BANNER);
adView.setAdUnitId("ca-app-pub-5396385527314971/1328911094");
layout.addView(adView);
}
if (adView != null) {
AdRequest adRequest = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.build();
adView.loadAd(adRequest);
}
}
class MapItemOverlay extends ItemizedOverlay<MapLocationOverlay> {
protected List<MapLocationOverlay> items = new ArrayList<MapLocationOverlay>();
public MapItemOverlay(Drawable defaultMarker) {
super(defaultMarker);
populate();
}
@Override
protected MapLocationOverlay createItem(int paramInt) {
return items.get(paramInt);
}
@Override
public int size() {
return items.size();
}
public void addItem(MapLocationOverlay item) {
setLastFocusedIndex(-1);
items.add(item);
populate();
}
public void removeAllItems() {
setLastFocusedIndex(-1);
items.clear();
populate();
}
}
@Override
protected boolean isRouteDisplayed() {
return false;
}
class MapLocationOverlay<T> extends OverlayItem {
private T item;
public MapLocationOverlay(String label, double latitude,
double longitude, T item) {
super(
new GeoPoint((int) (latitude * 1e6),
(int) (longitude * 1e6)), label, null);
this.item = item;
}
public T getItem() {
return item;
}
}
protected void showError(String message) {
showError(message, false);
}
protected void showError(String message, final boolean finish) {
AlertDialog.Builder build = new AlertDialog.Builder(this);
build.setMessage(message);
build.setPositiveButton("Ok",
new android.content.DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if (finish)
finish();
}
});
build.create().show();
}
}