package net.osmand.plus; import android.app.Activity; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import net.osmand.CallbackWithObject; import net.osmand.Location; import net.osmand.plus.helpers.GpxUiHelper; import net.osmand.plus.routing.RouteProvider.GPXRouteParamsBuilder; import java.util.ArrayList; import java.util.List; public class OsmAndLocationSimulation { private Thread routeAnimation; private OsmAndLocationProvider provider; private OsmandApplication app; public OsmAndLocationSimulation(OsmandApplication app, OsmAndLocationProvider provider){ this.app = app; this.provider = provider; } public boolean isRouteAnimating() { return routeAnimation != null; } // public void startStopRouteAnimationRoute(final MapActivity ma) { // if (!isRouteAnimating()) { // List<Location> currentRoute = app.getRoutingHelper().getCurrentRoute(); // if (currentRoute.isEmpty()) { // Toast.makeText(app, R.string.animate_routing_route_not_calculated, Toast.LENGTH_LONG).show(); // } else { // startAnimationThread(app.getRoutingHelper(), ma, new ArrayList<Location>(currentRoute), false, 1); // } // } else { // stop(); // } // } public void startStopRouteAnimation(final Activity ma, final Runnable runnable) { if (!isRouteAnimating()) { AlertDialog.Builder builder = new AlertDialog.Builder(ma); builder.setTitle(R.string.animate_route); final View view = ma.getLayoutInflater().inflate(R.layout.animate_route, null); final View gpxView = ((LinearLayout) view.findViewById(R.id.layout_animate_gpx)); final RadioButton radioGPX = (RadioButton) view.findViewById(R.id.radio_gpx); radioGPX.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { gpxView.setVisibility(isChecked ? View.VISIBLE : View.GONE); } }); ((TextView) view.findViewById(R.id.MinSpeedup)).setText("1"); //$NON-NLS-1$ ((TextView) view.findViewById(R.id.MaxSpeedup)).setText("4"); //$NON-NLS-1$ final SeekBar speedup = (SeekBar) view.findViewById(R.id.Speedup); speedup.setMax(3); builder.setView(view); builder.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { boolean gpxNavigation = radioGPX.isChecked(); if (gpxNavigation) { GpxUiHelper.selectGPXFile(ma, false, false, new CallbackWithObject<GPXUtilities.GPXFile[]>() { @Override public boolean processResult(GPXUtilities.GPXFile[] result) { GPXRouteParamsBuilder builder = new GPXRouteParamsBuilder(result[0], app.getSettings()); startAnimationThread(app, builder.getPoints(), true, speedup.getProgress() + 1); if (runnable != null) { runnable.run(); } return true; } }); } else { List<Location> currentRoute = app.getRoutingHelper().getCurrentCalculatedRoute(); if (currentRoute.isEmpty()) { Toast.makeText(app, R.string.animate_routing_route_not_calculated, Toast.LENGTH_LONG).show(); } else { startAnimationThread(app, new ArrayList<Location>(currentRoute), false, 1); if (runnable != null) { runnable.run(); } } } } }); builder.setNegativeButton(R.string.shared_string_cancel, null); builder.show(); } else { stop(); } } public void startStopRouteAnimation(final Activity ma) { startStopRouteAnimation(ma, null); } private void startAnimationThread(final OsmandApplication app, final List<Location> directions, final boolean useLocationTime, final float coeff) { final float time = 1.5f; routeAnimation = new Thread() { @Override public void run() { Location current = directions.isEmpty() ? null : new Location(directions.remove(0)); Location prev = current; long prevTime = current == null ? 0 : current.getTime(); float meters = metersToGoInFiveSteps(directions, current); while (!directions.isEmpty() && routeAnimation != null) { int timeout = (int) (time * 1000); float intervalTime = time; if(useLocationTime) { current = directions.remove(0); meters = current.distanceTo(prev); if (!directions.isEmpty()) { timeout = (int) (directions.get(0).getTime() - current.getTime()); intervalTime = (current.getTime() - prevTime) / 1000f; prevTime = current.getTime(); } } else { if (current.distanceTo(directions.get(0)) > meters) { current = middleLocation(current, directions.get(0), meters); } else { current = new Location(directions.remove(0)); meters = metersToGoInFiveSteps(directions, current); } } if(intervalTime != 0) { current.setSpeed(meters / intervalTime * coeff); } current.setTime(System.currentTimeMillis()); if(!current.hasAccuracy() || Double.isNaN(current.getAccuracy())) { current.setAccuracy(5); } if (prev != null && prev.distanceTo(current) > 3) { current.setBearing(prev.bearingTo(current)); } final Location toset = current; app.runInUIThread(new Runnable() { @Override public void run() { provider.setLocationFromSimulation(toset); } }); try { Thread.sleep((long)(timeout / coeff)); } catch (InterruptedException e) { // do nothing } prev = current; } OsmAndLocationSimulation.this.stop(); } }; routeAnimation.start(); } private float metersToGoInFiveSteps( final List<Location> directions, Location current) { return directions.isEmpty() ? 20.0f : Math.max(20.0f, current.distanceTo(directions.get(0)) / 2 ); } public void stop() { routeAnimation = null; } public static Location middleLocation(Location start, Location end, float meters) { double lat1 = toRad(start.getLatitude()); double lon1 = toRad(start.getLongitude()); double R = 6371; // radius of earth in km double d = meters / 1000; // in km float brng = (float) (toRad(start.bearingTo(end))); double lat2 = Math.asin(Math.sin(lat1) * Math.cos(d / R) + Math.cos(lat1) * Math.sin(d / R) * Math.cos(brng)); double lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d / R) * Math.cos(lat1), Math.cos(d / R) - Math.sin(lat1) * Math.sin(lat2)); Location nl = new Location(start); nl.setLatitude(toDegree(lat2)); nl.setLongitude(toDegree(lon2)); nl.setBearing(brng); return nl; } private static double toDegree(double radians) { return radians * 180 / Math.PI; } private static double toRad(double degree) { return degree * Math.PI / 180; } }