package net.osmand.plus.views;
import net.osmand.LogUtil;
import net.osmand.osm.MapUtils;
import org.apache.commons.logging.Log;
import android.util.FloatMath;
/**
* Thread for animated dragging.
* Defines accelerator to stop dragging screen.
*/
public class AnimateDraggingMapThread implements Runnable {
protected static final Log log = LogUtil.getLog(AnimateDraggingMapThread.class);
public interface AnimateDraggingCallback {
public void dragTo(float curX, float curY, float newX, float newY, boolean notify);
public void setLatLon(double latitude, double longitude, boolean notify);
public void zoomTo(float zoom, boolean notify);
public void rotateTo(float rotate);
public float getRotate();
}
private boolean animateDrag = true;
private float curX;
private float curY;
private float vx;
private float vy;
private float ax;
private float ay;
private byte dirX;
private byte dirY;
private final float a = 0.0014f;
private long time;
private volatile boolean stopped;
// 0 - zoom out, 1 - moving, 2 - zoom in
private byte phaseOfMoving ;
private int endZ;
private byte dirZ;
private int intZ;
private byte dirIntZ;
private float curZ;
private int timeZEnd;
private int timeZInt;
private int timeMove;
private float moveX;
private float moveY;
private double moveLat;
private double moveLon;
private volatile Thread currentThread = null;
private AnimateDraggingCallback callback = null;
private boolean notifyListener;
private double targetLatitude = 0;
private double targetLongitude = 0;
private int targetZoom = 0;
private float targetRotate = 0;
@Override
public void run() {
currentThread = Thread.currentThread();
try {
boolean conditionToCountinue = true;
while (!stopped && conditionToCountinue) {
// calculate sleep
long sleep = 0;
if(animateDrag){
// sleep = (long) (40d / (Math.max(vx, vy) + 0.45));
sleep = 80;
} else {
sleep = 80;
}
Thread.sleep(sleep);
long curT = System.currentTimeMillis();
int dt = (int) (curT - time);
float newX = animateDrag && vx > 0 ? curX + dirX * vx * dt : curX;
float newY = animateDrag && vy > 0 ? curY + dirY * vy * dt : curY;
float newZ = curZ;
if(!animateDrag){
if (phaseOfMoving == 0 || phaseOfMoving == 2) {
byte dir = phaseOfMoving == 2 ? dirZ : dirIntZ;
int time = phaseOfMoving == 2 ? timeZEnd : timeZInt;
float end = phaseOfMoving == 2 ? endZ : intZ;
if (time > 0) {
newZ = newZ + dir * (float) dt / time;
}
if (dir > 0 == newZ > end) {
newZ = end;
}
} else {
if(timeMove > 0){
newX = newX + moveX * (float) dt / timeMove;
newY = newY + moveY * (float) dt / timeMove;
if(moveX > 0 == newX > moveX){
newX = moveX;
}
if(moveY > 0 == newY > moveY){
newY = moveY;
}
}
}
}
if (!stopped && callback != null) {
if (animateDrag || phaseOfMoving == 1) {
callback.dragTo(curX, curY, newX, newY, notifyListener);
} else {
callback.zoomTo(newZ, notifyListener);
}
}
time = curT;
if(animateDrag){
vx -= ax * dt;
vy -= ay * dt;
curX = newX;
curY = newY;
conditionToCountinue = vx > 0.5 || vy > 0.5;
} else {
if(phaseOfMoving == 0){
curZ = newZ;
if(curZ == intZ){
curX = 0;
curY = 0;
phaseOfMoving ++;
}
} else if(phaseOfMoving == 2){
curZ = newZ;
conditionToCountinue = curZ != endZ;
} else {
curX = newX;
curY = newY;
if(curX == moveX && curY == moveY){
phaseOfMoving ++;
callback.setLatLon(moveLat, moveLon, notifyListener);
}
}
}
}
if(curZ != ((int) Math.round(curZ))){
if(Math.abs(curZ - endZ) > 3){
callback.zoomTo(Math.round(curZ), notifyListener);
} else {
callback.zoomTo(endZ, notifyListener);
}
}
//rotate after animation
conditionToCountinue = true;
while (conditionToCountinue && callback != null) {
conditionToCountinue = false;
float rotationDiff = targetRotate-callback.getRotate();
if (Math.abs((rotationDiff+360)%360) < Math.abs((rotationDiff-360)%360)) {
rotationDiff = (rotationDiff+360)%360;
} else {
rotationDiff = (rotationDiff-360)%360;
}
float absDiff = Math.abs(rotationDiff);
if (absDiff > 0) {
Thread.sleep(60);
if (absDiff < 1) {
callback.rotateTo(targetRotate);
} else {
conditionToCountinue = true;
callback.rotateTo(((absDiff/10)*Math.signum(rotationDiff) + callback.getRotate())%360);
}
}
}
} catch (InterruptedException e) {
}
currentThread = null;
}
/**
* Stop dragging async
*/
public void stopAnimating(){
stopped = true;
}
public boolean isAnimating(){
return currentThread != null;
}
/**
* Stop dragging sync
*/
public void stopAnimatingSync(){
// wait until current thread != null
stopped = true;
while(currentThread != null){
try {
currentThread.join();
} catch (InterruptedException e) {
}
}
}
public void startZooming(int zoomStart, int zoomEnd){
stopAnimatingSync();
targetZoom = 0;
this.notifyListener = false;
if(zoomStart < zoomEnd){
dirZ = 1;
} else {
dirZ = -1;
}
curZ = zoomStart;
endZ = zoomEnd;
timeZEnd = 600;
phaseOfMoving = 2;
animateDrag = false;
time = System.currentTimeMillis();
stopped = false;
Thread thread = new Thread(this,"Animatable dragging"); //$NON-NLS-1$
thread.start();
}
public void startMoving(double curLat, double curLon, double finalLat, double finalLon, int curZoom, int endZoom, int tileSize, float rotate, boolean notifyListener){
stopAnimatingSync();
targetLatitude = finalLat;
targetLongitude = finalLon;
targetZoom = endZoom;
this.notifyListener = notifyListener;
curZ = curZoom;
intZ = curZoom;
float mX = (float) ((MapUtils.getTileNumberX(intZ, curLon) - MapUtils.getTileNumberX(intZ, finalLon)) * tileSize);
float mY = (float) ((MapUtils.getTileNumberY(intZ, curLat) - MapUtils.getTileNumberY(intZ, finalLat)) * tileSize);
while (Math.abs(mX) + Math.abs(mY) > 1200 && intZ > 4) {
intZ--;
mX = (float) ((MapUtils.getTileNumberX(intZ, curLon) - MapUtils.getTileNumberX(intZ, finalLon)) * tileSize);
mY = (float) ((MapUtils.getTileNumberY(intZ, curLat) - MapUtils.getTileNumberY(intZ, finalLat)) * tileSize);
}
float rad = (float) Math.toRadians(rotate);
moveX = FloatMath.cos(rad) * mX - FloatMath.sin(rad) * mY;
moveY = FloatMath.sin(rad) * mX + FloatMath.cos(rad) * mY;
moveLat = finalLat;
moveLon = finalLon;
if(curZoom < intZ){
dirIntZ = 1;
} else {
dirIntZ = -1;
}
if(intZ < endZoom){
dirZ = 1;
} else {
dirZ = -1;
}
endZ = endZoom;
// timeZInt = Math.abs(curZoom - intZ) * 300;
// if (timeZInt > 900) {
//
// }
timeZInt = 600;
timeZEnd = 500;
timeMove = (int) (Math.abs(moveX) + Math.abs(moveY) * 3);
if(timeMove > 2200){
timeMove = 2200;
}
animateDrag = false;
phaseOfMoving = (byte) (intZ == curZoom ? 1 : 0);
curX = 0;
curY = 0;
time = System.currentTimeMillis();
stopped = false;
Thread thread = new Thread(this,"Animatable dragging"); //$NON-NLS-1$
thread.start();
}
public void startDragging(float velocityX, float velocityY, float startX, float startY, float endX, float endY){
stopAnimatingSync();
targetZoom = 0;
this.notifyListener = true;
vx = velocityX;
vy = velocityY;
dirX = (byte) (endX > startX ? 1 : -1);
dirY = (byte) (endY > startY ? 1 : -1);
animateDrag = true;
ax = vx * a;
ay = vy * a;
time = System.currentTimeMillis();
stopped = false;
Thread thread = new Thread(this,"Animatable dragging"); //$NON-NLS-1$
thread.start();
}
public void startRotate(float rotate)
{
this.targetRotate = rotate;
if (!isAnimating()) {
//stopped = false;
//do we need to kill and recreate the thread? wait would be enough as now it
//also handles the rotation?
Thread thread = new Thread(this,"Animatable dragging"); //$NON-NLS-1$
thread.start();
}
}
public int getTargetZoom() {
return targetZoom;
}
public double getTargetLatitude() {
return targetLatitude;
}
public double getTargetLongitude() {
return targetLongitude;
}
public AnimateDraggingCallback getCallback() {
return callback;
}
public void setCallback(AnimateDraggingCallback callback) {
this.callback = callback;
}
}