package com.yanp.way.gps.activity;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Locale;
import org.joda.time.DateTime;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.text.Html;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
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.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.yanp.way.Chrono;
import com.yanp.way.Constants;
import com.yanp.way.R;
import com.yanp.way.gps.UserPosition;
import com.yanp.way.route.Route;
import com.yanp.way.route.downloaded.Step;
/*
* DO
* On affiche la position de l'utilisateur, uniquement via le GPS, pour plus de pr??cision.
* On affiche le parcours
* TODO
* Synchroniser la position de l'user avec celle du parcours :
* Lorsque l'user franchit une step, les informations de direction sont mise ?? jour.
* Trouver des ??l??ments graphiques sympa pour afficher les infos de directions
*
*/
/**
* Display the GPS navigation on a selected route.
* @author YPierru
*
*/
public class GPSNavigation extends Activity implements SensorEventListener,TextToSpeech.OnInitListener {
private LocationManager locationManager;
private TextToSpeech textToSpeech;
private MyLocationListener myLocationListener;
private ArrayList<LatLng> listPointsToFollow=new ArrayList<LatLng>();
private Route mRoute;
private ArrayList<Step> listSteps;
private GoogleMap googleMap;
private int totalDistance=0;
private int totalDuration=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_gps);
//The screen will not sleep during the navigaion
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getActionBar().hide();
this.googleMap = ((MapFragment) getFragmentManager()
.findFragmentById(R.id.mapGPS)).getMap();
this.googleMap.getUiSettings().setZoomControlsEnabled(false);
mRoute = getIntent().getExtras().getParcelable("Route_for_navigation_gps");
this.textToSpeech = new TextToSpeech(this, this);
initTotalDurationDistance();
initListPointsToFollow();
//On dessine le trajet
drawRoute();
}
/**
* Get the total distance and duration of the route
*/
public void initTotalDurationDistance(){
this.listSteps=mRoute.getListSteps();
for(int i=0;i<this.listSteps.size();i++){
this.totalDistance+=this.listSteps.get(i).getDistance().getValue();
this.totalDuration+=this.listSteps.get(i).getDuration().getValue();
}
}
/**
* Init the list of the points (LatLng) the user will follow, from the list of the Steps
*/
public void initListPointsToFollow(){
for (int i = 0; i < this.listSteps.size(); i++) {
this.listPointsToFollow.add(new LatLng(this.listSteps.get(i).getStart_location().getLat(),
this.listSteps.get(i).getStart_location().getLng()));
if(i+1==this.listSteps.size()){
this.listPointsToFollow.add(new LatLng(this.listSteps.get(i).getEnd_location().getLat(),
this.listSteps.get(i).getEnd_location().getLng()));
}
}
}
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = this.textToSpeech.setLanguage(Constants.CURRENT_LANGUAGE);
if (result == TextToSpeech.LANG_MISSING_DATA ||
result == TextToSpeech.LANG_NOT_SUPPORTED) {
Log.e("error", "This Language is not supported");
}
} else {
Log.e("error", "Initilization Failed!");
}
}
@Override
public void onDestroy() {
super.onDestroy();
this.textToSpeech.shutdown();
}
/**
* Draw the route on the map
*/
private void drawRoute(){
ArrayList<LatLng> listPointsOverview = mRoute.getPointsWhoDrawsPolylineLatLng();
ArrayList<LatLng> listStepMarkers = mRoute.getListMarkersLatLng();
LatLng startingPoint = listPointsOverview.get(0);
LatLng destinationPoint = listPointsOverview.get(listPointsOverview.size()-1);
setMarker(startingPoint, getResources().getString(R.string.start), false);
CameraUpdate cu = CameraUpdateFactory.newLatLngZoom(startingPoint,Constants.ZOOM_GENERAL);
this.googleMap.animateCamera(cu, Constants.ZOOM_SPEED_MS, null);
/*
* Place the marker on the map
*/
if(listStepMarkers.size()>2){
for(int i=1;i<listStepMarkers.size()-1;i++){
setMarker(listStepMarkers.get(i), getResources().getString(R.string.marker)+" "+i, true);
}
}
/*
* Draw the line
*/
PolylineOptions options = new PolylineOptions()
.geodesic(false)
.width(Constants.WIDTH_POLYLINE_GPS)
.color(Constants.COLOR_POLYLINE_GPS);
for (int i = 0; i < listPointsOverview.size(); i++) {
options.add(listPointsOverview.get(i));
}
this.googleMap.addPolyline(options);
setMarker(destinationPoint, getResources().getString(R.string.arrival), false);
}
/**
*
* @param point LatLng- The LatLng point where you want the marker
* @param str String- The message for the info window
* @param isInter boolean- if true, the marker is an intermediary point (little image), if not it's the starting/destination point (big image)
*/
public void setMarker(LatLng point, String str, boolean isInter) {
int markerIcn;
if(isInter){
markerIcn=R.drawable.ic_marker_inter;
}else{
markerIcn=R.drawable.ic_marker_princ;
}
this.googleMap.addMarker(
new MarkerOptions()
.icon(BitmapDescriptorFactory
.fromResource(markerIcn))
.anchor(0.0f, 1.0f) // Anchors the
// marker on the
// bottom left
.position(point).title(str)).showInfoWindow();
}
@Override
protected void onResume() {
super.onResume();
this.myLocationListener = new MyLocationListener(this.googleMap, this.listSteps, this.textToSpeech, this.listPointsToFollow, this.totalDistance, this.totalDuration);
this.locationManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
if(Constants.NETWORK_GPS){
this.locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
Constants.MIN_TIME_GPS_REQUEST_MS,
Constants.MIN_DIST_GPS_REQUEST_M,
this.myLocationListener);
}else{
if (!this.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
buildAlertMessageNoGps();
}else{
this.locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
Constants.MIN_TIME_GPS_REQUEST_MS,
Constants.MIN_DIST_GPS_REQUEST_M,
this.myLocationListener);
}
}
}
/**
* Shows the dialog if the user doesn't have enable the GPS location.
*/
private void buildAlertMessageNoGps() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(
getResources().getString(R.string.alert_gps_disable))
.setCancelable(false)
.setPositiveButton(getResources().getString(R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog,final int id) {
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
})
.setNegativeButton(getResources().getString(R.string.exit), new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int id) {
dialog.cancel();
finish();
}
});
final AlertDialog alert = builder.create();
alert.show();
}
@Override
protected void onPause() {
super.onPause();
this.locationManager.removeUpdates(this.myLocationListener);
}
/**
* Custom location listener.
* Core of the gps navigation.
* @author YPierru
*
*/
private class MyLocationListener implements LocationListener {
private int distanceOfTheStep;
private int totalRemainingDist;
private int distUserNextPoint;
private int secondsChrono;
private int indexCurrentPoint;
private int stepDuration=0;
private int remainingTimeBeforeNextPoint=0;
private int totalDistance;
private int totalDuration;
private Step currentStep;
private Step nextStep=null;
private boolean imlate=false;
private boolean isSpeakingBeforeRoute=false;
private boolean allInfoAreDisplaying=false;
private boolean mustSpeak1000=true,mustSpeak500=true,mustSpeak200=true,mustSpeak50=true;
private GoogleMap googleMap;
private Chrono chronometer;
private ArrayList<Step> listSteps;
private ArrayList<LatLng> listPointsToFollow;
private UserPosition userPosition;
private TextToSpeech textToSpeech;
public MyLocationListener(GoogleMap googleMap, ArrayList<Step> listSteps, TextToSpeech textToSpeech, ArrayList<LatLng> listPointsToFollow, int totalDistance, int totalDuration) {
this.googleMap=googleMap;
this.chronometer=new Chrono();
this.listSteps=listSteps;
this.userPosition = new UserPosition();
this.textToSpeech=textToSpeech;
this.listPointsToFollow=listPointsToFollow;
this.totalDistance=totalDistance;
this.totalDuration=totalDuration;
}
/**
* When the position of my user change
*/
@Override
public void onLocationChanged(Location location) {
this.indexCurrentPoint = this.userPosition.getIndexPointToFollow();
this.userPosition.setCurrentPos(location.getLatitude() ,
location.getLongitude(),
location.getBearing(),
this.googleMap);
this.distUserNextPoint=formatDist(distanceBetween(this.userPosition.getCurrentPos(),this.listPointsToFollow.get(this.indexCurrentPoint)));
this.currentStep=this.listSteps.get(this.indexCurrentPoint);
if(this.indexCurrentPoint<this.listPointsToFollow.size()-1){
this.nextStep=this.listSteps.get(this.indexCurrentPoint+1);
}
/*
* Calcul the estimate remaining time before reaching the next point
*/
calculateRemainingTimeBeforeNextPoint();
if(!this.userPosition.isFollowingNavigation()){
checkIfUserIsReachingTheStartingPoint();
}
else{
displayDist();
this.distanceOfTheStep=this.listSteps.get(this.indexCurrentPoint).getDistance().getValue();
this.totalRemainingDist=this.totalDistance-(this.distanceOfTheStep-this.distUserNextPoint);
displayInfoFinish();
speakAccordingToDistanceBetweenUserAndNextPoint();
if(this.distUserNextPoint<Constants.RADIUS_DETECTION){
actionWhenUserReachNextPoint();
}
}
}
public void actionWhenUserReachNextPoint(){
this.mustSpeak1000=true;
this.mustSpeak200=true;
this.mustSpeak50=true;
this.mustSpeak500=true;
this.chronometer.stop();
this.secondsChrono=(int)this.chronometer.getSeconds();
if(this.secondsChrono>=this.stepDuration){
this.imlate=true;
this.remainingTimeBeforeNextPoint=this.secondsChrono-this.stepDuration;
}else{
this.imlate=false;
this.remainingTimeBeforeNextPoint=this.stepDuration-this.secondsChrono;
}
this.chronometer.start();
if(this.indexCurrentPoint<this.listPointsToFollow.size()-1){
this.userPosition.setToNextPointToFollow();
displayInstructions();
this.totalDistance=this.totalDistance-this.distanceOfTheStep;
this.distUserNextPoint=formatDist(distanceBetween(this.userPosition.getCurrentPos(), this.listPointsToFollow.get(this.indexCurrentPoint+1)));
this.textToSpeech.speak(getResources().getString(R.string.gps_in)+" "+this.distUserNextPoint+" "+getResources().getString(R.string.gps_distance_unit)+", "+Html.fromHtml(this.listSteps.get(this.indexCurrentPoint+1).getHtml_instructions()).toString(), TextToSpeech.QUEUE_FLUSH, null);
if(this.distUserNextPoint>500 && this.distUserNextPoint <=1000){
this.mustSpeak1000=false;
}else if(this.distUserNextPoint>200 && this.distUserNextPoint <=500){
this.mustSpeak500=false;
}else if(this.distUserNextPoint>50 && this.distUserNextPoint <=200){
this.mustSpeak200=false;
}else if(this.distUserNextPoint<=50){
this.mustSpeak50=false;
}
}
else{
this.userPosition.setFollowingNavigation(false);
this.textToSpeech.speak(getResources().getString(R.string.gps_arrival_phrase), TextToSpeech.QUEUE_FLUSH, null);
}
}
public void speakAccordingToDistanceBetweenUserAndNextPoint(){
if(500<this.distUserNextPoint && this.distUserNextPoint<=1000 && this.mustSpeak1000){
this.mustSpeak1000=false;
this.textToSpeech.speak(getResources().getString(R.string.gps_in)+" "+this.distUserNextPoint+" "+getResources().getString(R.string.gps_distance_unit)+", "+Html.fromHtml(this.listSteps.get(this.indexCurrentPoint).getHtml_instructions()).toString(), TextToSpeech.QUEUE_ADD, null);
}
if(200<this.distUserNextPoint && this.distUserNextPoint<=500 && this.mustSpeak500){
this.mustSpeak500=false;
this.textToSpeech.speak(getResources().getString(R.string.gps_in)+" "+this.distUserNextPoint+" "+getResources().getString(R.string.gps_distance_unit)+", "+Html.fromHtml(this.listSteps.get(this.indexCurrentPoint).getHtml_instructions()).toString(), TextToSpeech.QUEUE_ADD, null);
}
if(50<this.distUserNextPoint && this.distUserNextPoint<=200 && this.mustSpeak200){
this.mustSpeak200=false;
this.textToSpeech.speak(getResources().getString(R.string.gps_in)+" "+this.distUserNextPoint+" "+getResources().getString(R.string.gps_distance_unit)+", "+Html.fromHtml(this.listSteps.get(this.indexCurrentPoint).getHtml_instructions()).toString(), TextToSpeech.QUEUE_ADD, null);
}
if(this.distUserNextPoint<=50 && this.mustSpeak50){
this.mustSpeak50=false;
this.textToSpeech.speak(getResources().getString(R.string.gps_in)+" "+this.distUserNextPoint+" "+getResources().getString(R.string.gps_distance_unit)+", "+Html.fromHtml(this.listSteps.get(this.indexCurrentPoint).getHtml_instructions()).toString(), TextToSpeech.QUEUE_ADD, null);
}
}
public void checkIfUserIsReachingTheStartingPoint(){
if(this.distUserNextPoint<Constants.RADIUS_DETECTION && !this.allInfoAreDisplaying){
actionWhenUserReachTheStartingPoint();
}
else if(this.distUserNextPoint>Constants.RADIUS_DETECTION){
actionWhenUserDidNotReachTheStartingPoint();
}
}
public void actionWhenUserReachTheStartingPoint(){
this.allInfoAreDisplaying=true;
this.textToSpeech.speak(Html.fromHtml(this.currentStep.getHtml_instructions()).toString()+". "+getResources().getString(R.string.gps_then)+", "+Html.fromHtml(this.nextStep.getHtml_instructions()).toString(), TextToSpeech.QUEUE_FLUSH, null);
this.chronometer.start();
setVisibleLayout();
displayInstructions();
this.userPosition.setFollowingNavigation(true);
this.userPosition.setToNextPointToFollow();
}
public void actionWhenUserDidNotReachTheStartingPoint(){
if(!this.isSpeakingBeforeRoute){
this.isSpeakingBeforeRoute=true;
String text=getResources().getString(R.string.gps_go_starting_point)+", "+mRoute.getStartAddress();
this.textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null);
}
setVisibleInfoDirection();
displayInstructions(getResources().getString(R.string.gps_youre_at)+" "+convertMeterToKm(this.distUserNextPoint)+" "+getResources().getString(R.string.gps_from_starting_point));
}
public void calculateRemainingTimeBeforeNextPoint(){
int stepDuration;
if(this.chronometer.isStart()){
stepDuration=this.listSteps.get(this.indexCurrentPoint).getDuration().getValue();
this.secondsChrono=(int)this.chronometer.getSeconds();
if(this.secondsChrono>=stepDuration){
this.imlate=true;
this.remainingTimeBeforeNextPoint=this.secondsChrono-stepDuration;
}
}
}
/**
* Calcul the distance between 2 points
* @param sourcePoint
* @param targetPoint
* @return the distance between sourcePoint and targetPoint, in meters
*/
public int distanceBetween(LatLng sourcePoint, LatLng targetPoint) {
double lat1 = sourcePoint.latitude;
double lng1 = sourcePoint.longitude;
double lat2 = targetPoint.latitude;
double lng2 = targetPoint.longitude;
double earthRadius = Constants.EARTH_RADIUS;
double dLat = Math.toRadians(lat2 - lat1);
double dLng = Math.toRadians(lng2 - lng1);
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos(Math.toRadians(lat1))
* Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2)
* Math.sin(dLng / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double dist = earthRadius * c;
int meterConversion = 1609;
return (int) (dist * meterConversion);
}
/**
* Display the informations about the end of the route
*/
public void displayInfoFinish(){
TextView tv_gpsfinish = (TextView)findViewById(R.id.tv_gps_finish);
int[] hoursMinutesToFinish=formatDuration(this.totalDuration);
int[] hoursMinutesDay=getHoursMinutesToday();
int[] hoursMinutesDelta=formatDuration(this.remainingTimeBeforeNextPoint);
int hourFinish=hoursMinutesDay[0]+hoursMinutesToFinish[0];
int minuteFinish=hoursMinutesDay[1]+hoursMinutesToFinish[1];
if(imlate){
hourFinish+=hoursMinutesDelta[0];
minuteFinish+=hoursMinutesDelta[1];
}else{
hourFinish-=hoursMinutesDelta[0];
minuteFinish-=hoursMinutesDelta[1];
}
/*
* TODO
* Faire en sorte d'avoir l'heure qui passe ?? 00h si
* par exemple il est 23h30 et trajet > 30 min
*/
tv_gpsfinish.setText(convertMeterToKm(this.totalRemainingDist)+" | "+hourFinish+"h"+minuteFinish);
}
/**
* Display the distance remaining before reaching the next point
*/
public void displayDist(){
TextView tv_DistNextPoint = (TextView)findViewById(R.id.tv_gps_kmnextpoint);
tv_DistNextPoint.setText(convertMeterToKm(this.distUserNextPoint));
}
public int[] formatDuration(int duration){
int hours = duration / 3600;
int minutes = (duration % 3600) / 60;
int rtr[]={hours,minutes};
return rtr;
}
public int[] getHoursMinutesToday(){
DateTime dt=new DateTime();
int rtr[]={dt.getHourOfDay(),dt.getMinuteOfHour()};
return rtr;
}
public String convertMeterToKm(int d){
if(d>1000){
double distKm=d/(float)1000;
DecimalFormat format=new DecimalFormat("#.#");
return format.format(distKm)+"km";
}else{
return d+"m";
}
}
/**
* Set visible the 3 panels
*/
public void setVisibleLayout(){
setVisibleInfoFinish();
setVisibleKmNextPoint();
setVisibleInfoDirection();
}
public void setVisibleInfoFinish(){
TextView tv_infoFinish=(TextView)findViewById(R.id.tv_gps_finish);
if(tv_infoFinish.getVisibility()!=View.VISIBLE){
tv_infoFinish.setVisibility(View.VISIBLE);
}
}
public void setVisibleKmNextPoint(){
TextView tv_kmNextPoint=(TextView)findViewById(R.id.tv_gps_kmnextpoint);
if(tv_kmNextPoint.getVisibility()!=View.VISIBLE){
tv_kmNextPoint.setVisibility(View.VISIBLE);
}
}
public void setVisibleInfoDirection(){
TextView tv_infoDirection=(TextView)findViewById(R.id.tv_gps_htmlInstructions);
if(tv_infoDirection.getVisibility()!=View.VISIBLE){
tv_infoDirection.setVisibility(View.VISIBLE);
}
}
/**
* Display the instructions for reaching the next point
*/
public void displayInstructions(){
TextView tv_Instructions = (TextView)findViewById(R.id.tv_gps_htmlInstructions);
tv_Instructions.setText(Html.fromHtml(this.nextStep.getHtml_instructions().split("<div ")[0]));
}
/**
* Display the "infoToDisplay"
* @param infoToDisplay
*/
public void displayInstructions(String infoToDisplay){
TextView tv_Instructions = (TextView)findViewById(R.id.tv_gps_htmlInstructions);
tv_Instructions.setText(Html.fromHtml(infoToDisplay));
}
/**
* Format a distance, so it will end in 0 or 5
* @param distance
* @return the formated distance (int)
*/
public int formatDist(int distance){
int lastDigit=distance%10;
switch(lastDigit){
case 1: case 2: case 3:
distance=distance-lastDigit;
break;
case 4: case 6:
distance=distance-lastDigit+5;
break;
case 7: case 8 : case 9:
distance=distance-lastDigit+10;
break;
}
return distance;
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
//
}
@Override
public void onProviderEnabled(String provider) {
//
}
@Override
public void onProviderDisabled(String provider) {
//
}
}
@Override
public void onSensorChanged(SensorEvent event) {
//
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
//
}
}