diff --git a/vtm-gdx-html/src/org/oscim/gdx/client/SearchBox.java b/vtm-gdx-html/src/org/oscim/gdx/client/SearchBox.java
index 398c21c8..968c412b 100644
--- a/vtm-gdx-html/src/org/oscim/gdx/client/SearchBox.java
+++ b/vtm-gdx-html/src/org/oscim/gdx/client/SearchBox.java
@@ -237,9 +237,9 @@ public class SearchBox {
 					if (b.maxLatitudeE6 - b.minLatitudeE6 < 100 &&
 							b.maxLongitudeE6 - b.minLongitudeE6 < 100)
 						// for small bbox use zoom=16 to get an overview
-						map.getViewport().animateTo(500, b.getCenterPoint(), 1 << 16, false);
+						map.getAnimator().animateTo(500, b.getCenterPoint(), 1 << 16, false);
 					else
-						map.getViewport().animateTo(b);
+						map.getAnimator().animateTo(b);
 					if (d instanceof NominatimData && ((NominatimData) d).getWkt() != null) {
 						String wkt = ((NominatimData) d).getWkt();
 
diff --git a/vtm-gdx/src/org/oscim/gdx/GdxMap.java b/vtm-gdx/src/org/oscim/gdx/GdxMap.java
index 334881c7..2316853f 100644
--- a/vtm-gdx/src/org/oscim/gdx/GdxMap.java
+++ b/vtm-gdx/src/org/oscim/gdx/GdxMap.java
@@ -365,12 +365,12 @@ public class GdxMap implements ApplicationListener {
 
 			if (amount > 0) {
 
-				mMapPosition.animateZoom(150, 0.8f, 0, 0);
+				mMap.getAnimator().animateZoom(150, 0.8f, 0, 0);
 			} else {
 				float fx = mPosX - mMap.getWidth() / 2;
 				float fy = mPosY - mMap.getHeight() / 2;
 
-				mMapPosition.animateZoom(150, 1.25f, fx, fy);
+				mMap.getAnimator().animateZoom(150, 1.25f, fx, fy);
 			}
 			mMap.updateMap(false);
 
@@ -438,7 +438,7 @@ public class GdxMap implements ApplicationListener {
 			//Log.d("", "fling " + button + " " + velocityX + "/" + velocityY);
 			if (mayFling && button == Buttons.LEFT) {
 				int m = Tile.SIZE * 4;
-				mMapPosition.animateFling((int) velocityX, (int) velocityY, -m, m, -m, m);
+				mMap.getAnimator().animateFling((int) velocityX, (int) velocityY, -m, m, -m, m);
 				return true;
 			}
 			return false;
diff --git a/vtm/src/org/oscim/layers/MapEventLayer.java b/vtm/src/org/oscim/layers/MapEventLayer.java
index 0bab7754..5627eb05 100644
--- a/vtm/src/org/oscim/layers/MapEventLayer.java
+++ b/vtm/src/org/oscim/layers/MapEventLayer.java
@@ -299,7 +299,7 @@ public class MapEventLayer extends InputLayer {
 		int w = Tile.SIZE * 3;
 		int h = Tile.SIZE * 3;
 
-		mMapPosition.animateFling(
+		mMap.getAnimator().animateFling(
 				Math.round(velocityX),
 				Math.round(velocityY),
 				-w, w, -h, h);
diff --git a/vtm/src/org/oscim/renderer/GLRenderer.java b/vtm/src/org/oscim/renderer/GLRenderer.java
index c5a68d44..f4d45841 100644
--- a/vtm/src/org/oscim/renderer/GLRenderer.java
+++ b/vtm/src/org/oscim/renderer/GLRenderer.java
@@ -294,8 +294,7 @@ public class GLRenderer {
 		MapPosition pos = mMapPosition;
 
 		synchronized (mViewport) {
-			// update MapPosition
-			mViewport.updateAnimation();
+			mMap.getAnimator().updateAnimation();
 
 			// get current MapPosition
 			changed = mViewport.getMapPosition(pos);
diff --git a/vtm/src/org/oscim/view/Map.java b/vtm/src/org/oscim/view/Map.java
index a585804a..5a7cd4e8 100644
--- a/vtm/src/org/oscim/view/Map.java
+++ b/vtm/src/org/oscim/view/Map.java
@@ -35,6 +35,8 @@ public abstract class Map {
 
 	private final Layers mLayers;
 	private final Viewport mViewport;
+	private final MapAnimator mAnimator;
+
 	private final MapPosition mMapPosition;
 	private final AsyncExecutor mAsyncExecutor;
 
@@ -49,11 +51,12 @@ public abstract class Map {
 	public Map() {
 
 		mViewport = new Viewport(this);
+		mAnimator = new MapAnimator(this, mViewport);
+
 		mMapPosition = new MapPosition();
 		mLayers = new Layers();
 		mAsyncExecutor = new AsyncExecutor(2);
 
-		// FIXME!
 		mDebugSettings = new DebugSettings();
 		MapTileLoader.setDebugSettings(mDebugSettings);
 
@@ -216,4 +219,8 @@ public abstract class Map {
 	public BoundingBox getBoundingBox() {
 		return mViewport.getViewBox();
 	}
+
+	public MapAnimator getAnimator() {
+		return mAnimator;
+	}
 }
diff --git a/vtm/src/org/oscim/view/MapAnimator.java b/vtm/src/org/oscim/view/MapAnimator.java
new file mode 100644
index 00000000..06b7160d
--- /dev/null
+++ b/vtm/src/org/oscim/view/MapAnimator.java
@@ -0,0 +1,274 @@
+package org.oscim.view;
+
+import org.oscim.core.BoundingBox;
+import org.oscim.core.GeoPoint;
+import org.oscim.core.MapPosition;
+import org.oscim.core.MercatorProjection;
+import org.oscim.core.Point;
+import org.oscim.core.Tile;
+import org.oscim.utils.FastMath;
+
+// TODO: rewrite
+
+public class MapAnimator {
+
+	//private static final String TAG = MapAnimator.class.getName();
+
+	public MapAnimator(Map map, Viewport viewport) {
+		mViewport = viewport;
+		mMap = map;
+	}
+
+	private final Map mMap;
+	private final Viewport mViewport;
+
+	private final MapPosition mPos = new MapPosition();
+	private final MapPosition mStartPos = new MapPosition();
+	private final MapPosition mDeltaPos = new MapPosition();
+
+	private final Point mScroll = new Point();
+	private final Point mPivot = new Point();
+	private final Point mVelocity = new Point();
+
+	private double mScaleBy;
+
+	private float mDuration = 500;
+	private long mAnimEnd = -1;
+
+	private boolean mAnimMove;
+	private boolean mAnimFling;
+	private boolean mAnimScale;
+
+	public synchronized void animateTo(BoundingBox bbox) {
+		// TODO for large distatance first scale out, then in
+
+		// calculate the maximum scale at which the bbox is completely visible
+		double dx = Math.abs(MercatorProjection.longitudeToX(bbox.getMaxLongitude())
+				- MercatorProjection.longitudeToX(bbox.getMinLongitude()));
+
+		double dy = Math.abs(MercatorProjection.latitudeToY(bbox.getMinLatitude())
+				- MercatorProjection.latitudeToY(bbox.getMaxLatitude()));
+
+		double zx = mMap.getWidth() / (dx * Tile.SIZE);
+		double zy = mMap.getHeight() / (dy * Tile.SIZE);
+		double newScale = Math.min(zx, zy);
+
+		animateTo(500, bbox.getCenterPoint(), newScale, false);
+	}
+
+	public synchronized void animateTo(long duration, GeoPoint geoPoint, double scale,
+			boolean relative) {
+
+		mViewport.getMapPosition(mPos);
+
+		if (relative) {
+			if (mAnimEnd > 0 && mAnimScale)
+				scale = mDeltaPos.scale * scale;
+			else
+				scale = mPos.scale * scale;
+		}
+
+		scale = FastMath.clamp(scale, Viewport.MIN_SCALE, Viewport.MAX_SCALE);
+		mDeltaPos.scale = scale;
+
+		scale = (float) (scale / mPos.scale);
+
+		mScaleBy = mPos.scale * scale - mPos.scale;
+
+		mStartPos.scale = mPos.scale;
+		mStartPos.angle = mPos.angle;
+
+		mStartPos.x = mPos.x;
+		mStartPos.y = mPos.y;
+
+		mDeltaPos.x = MercatorProjection.longitudeToX(geoPoint.getLongitude());
+		mDeltaPos.y = MercatorProjection.latitudeToY(geoPoint.getLatitude());
+		mDeltaPos.x -= mStartPos.x;
+		mDeltaPos.y -= mStartPos.y;
+
+		mAnimMove = true;
+		mAnimScale = true;
+		mAnimFling = false;
+
+		animStart(duration);
+	}
+
+	public synchronized void animateZoom(long duration, double scale, float pivotX, float pivotY) {
+
+		mViewport.getMapPosition(mPos);
+
+		if (mAnimEnd > 0 && mAnimScale)
+			scale = mDeltaPos.scale * scale;
+		else
+			scale = mPos.scale * scale;
+
+		scale = FastMath.clamp(scale, Viewport.MIN_SCALE, Viewport.MAX_SCALE);
+		mDeltaPos.scale = scale;
+
+		scale = (float) (scale / mPos.scale);
+
+		mScaleBy = mPos.scale * scale - mPos.scale;
+
+
+		mStartPos.scale = mPos.scale;
+		mStartPos.angle = mPos.angle;
+
+		mPivot.x = pivotX;
+		mPivot.y = pivotY;
+
+		mAnimScale = true;
+		mAnimFling = false;
+		mAnimMove = false;
+
+		animStart(duration);
+	}
+
+	public synchronized void animateTo(GeoPoint geoPoint) {
+		animateTo(300, geoPoint, 1, true);
+	}
+
+	public synchronized void animateFling(int velocityX, int velocityY,
+			int minX, int maxX, int minY, int maxY) {
+
+		if (velocityX * velocityX + velocityY * velocityY < 2048)
+			return;
+
+		mViewport.getMapPosition(mPos);
+
+		mScroll.x = 0;
+		mScroll.y = 0;
+
+		float duration = 500;
+
+		// pi times thumb..
+		float flingFactor = (duration / 2500);
+		mVelocity.x = velocityX * flingFactor;
+		mVelocity.y = velocityY * flingFactor;
+		FastMath.clamp(mVelocity.x, minX, maxX);
+		FastMath.clamp(mVelocity.y, minY, maxY);
+
+		mAnimFling = true;
+		mAnimMove = false;
+		mAnimScale = false;
+		animStart(duration);
+	}
+
+
+	private void animStart(float duration) {
+		mDuration = duration;
+
+		mAnimEnd = System.currentTimeMillis() + (long) duration;
+		mMap.render();
+	}
+
+	private void animCancel() {
+		mAnimEnd = -1;
+		mAnimScale = false;
+		mAnimFling = false;
+		mAnimMove = false;
+
+		mPivot.x = 0;
+		mPivot.y = 0;
+	}
+
+	private boolean fling(float adv) {
+		synchronized (mViewport) {
+
+			adv = (float) Math.sqrt(adv);
+
+			double dx = mVelocity.x * adv;
+			double dy = mVelocity.y * adv;
+
+			if (dx == 0 && dy == 0)
+				return false;
+
+			mViewport.moveMap((float) (dx - mScroll.x), (float) (dy - mScroll.y));
+
+			mScroll.x = dx;
+			mScroll.y = dy;
+		}
+		return true;
+	}
+
+	/**
+	 * called by GLRenderer at begin of each frame.
+	 */
+	public void updateAnimation() {
+		if (mAnimEnd < 0)
+			return;
+
+		long millisLeft = mAnimEnd - System.currentTimeMillis();
+
+		synchronized (mViewport) {
+
+			// cancel animation when position was changed since last
+			// update, i.e. when it was modified outside the animator.
+			if (mViewport.getMapPosition(mPos)) {
+				animCancel();
+				return;
+			}
+
+			if (millisLeft <= 0) {
+				// set final position
+				if (mAnimMove && !mAnimFling)
+					mViewport.moveInternal(mStartPos.x + mDeltaPos.x, mStartPos.y + mDeltaPos.y);
+
+				if (mAnimScale) {
+					if (mScaleBy > 0)
+						doScale(mStartPos.scale + (mScaleBy - 1));
+					else
+						doScale(mStartPos.scale + mScaleBy);
+				}
+				mMap.updateMap(true);
+
+				animCancel();
+				return;
+			}
+
+			boolean changed = false;
+
+			float adv = (1.0f - millisLeft / mDuration);
+
+			if (mAnimScale) {
+				if (mScaleBy > 0)
+					doScale(mStartPos.scale + (mScaleBy * (Math.pow(2, adv) - 1)));
+				else
+					doScale(mStartPos.scale + (mScaleBy * adv));
+
+				changed = true;
+			}
+
+			if (mAnimMove) {
+				mViewport.moveInternal(
+						mStartPos.x + mDeltaPos.x * adv,
+						mStartPos.y + mDeltaPos.y * adv);
+
+				changed = true;
+			}
+
+			//if (mAnimMove && mAnimScale) {
+			//	mPos.angle = mStartPos.angle * (1 - adv);
+			//	updateMatrix();
+			//}
+
+			if (mAnimFling && fling(adv))
+				changed = true;
+
+			// continue animation
+			if (changed) {
+				// inform other layers that position has changed
+				mMap.updateMap(true);
+			} else {
+				// just render next frame
+				mMap.render();
+			}
+
+			// remember current map position
+			mViewport.getMapPosition(mPos);
+		}
+	}
+
+	private void doScale(double newScale) {
+		mViewport.scaleMap((float) (newScale / mPos.scale), (float)mPivot.x, (float)mPivot.y);
+	}
+}
diff --git a/vtm/src/org/oscim/view/Viewport.java b/vtm/src/org/oscim/view/Viewport.java
index 554adee7..36108f7c 100644
--- a/vtm/src/org/oscim/view/Viewport.java
+++ b/vtm/src/org/oscim/view/Viewport.java
@@ -28,7 +28,6 @@ import org.oscim.utils.Matrix4;
 public class Viewport {
 	//private static final String TAG = Viewport.class.getName();
 
-	// needs to fit for int: 2 * 20 * Tile.SIZE
 	public final static int MAX_ZOOMLEVEL = 20;
 	public final static int MIN_ZOOMLEVEL = 2;
 
@@ -37,24 +36,7 @@ public class Viewport {
 
 	private final static float MAX_TILT = 65;
 
-	private final Map mMap;
-
-	private double mAbsScale;
-	private double mAbsX;
-	private double mAbsY;
-
-	// mAbsScale * Tile.SIZE
-	// i.e. size of tile 0/0/0 at current scale in pixel
-	private double mCurScale;
-
-	// mAbsX * mCurScale
-	private double mCurX;
-
-	// mAbsY * mCurScale
-	private double mCurY;
-
-	private float mRotation;
-	private float mTilt;
+	private final MapPosition mPos = new MapPosition();
 
 	private final Matrix4 mProjMatrix = new Matrix4();
 	private final Matrix4 mProjMatrixI = new Matrix4();
@@ -81,22 +63,13 @@ public class Viewport {
 	public final static float VIEW_SCALE = (VIEW_NEAR / VIEW_DISTANCE) * 0.5f;
 
 	Viewport(Map map) {
-		mMap = map;
 
-		mAbsScale = 4;
-		mAbsX = 0.5;
-		mAbsY = 0.5;
+		mPos.scale = 4;
+		mPos.x = 0.5;
+		mPos.y = 0.5;
 
-		mRotation = 0;
-		mTilt = 0;
-
-		updatePosition();
-	}
-
-	private void updatePosition() {
-		mCurScale = mAbsScale * Tile.SIZE;
-		mCurX = mAbsX * mCurScale;
-		mCurY = mAbsY * mCurScale;
+		mPos.angle = 0;
+		mPos.tilt = 0;
 	}
 
 	public void setViewport(int width, int height) {
@@ -129,25 +102,23 @@ public class Viewport {
 	 */
 	public synchronized boolean getMapPosition(MapPosition pos) {
 
-		int z = FastMath.log2((int) mAbsScale);
-		//z = FastMath.clamp(z, MIN_ZOOMLEVEL, MAX_ZOOMLEVEL);
-		//float scale = (float) (mAbsScale / (1 << z));
+		int z = FastMath.log2((int) mPos.scale);
 
 		boolean changed = (pos.zoomLevel != z
-				|| pos.x != mAbsX
-				|| pos.y != mAbsY
-				|| pos.scale != mAbsScale
-				|| pos.angle != mRotation
-				|| pos.tilt != mTilt);
+				|| pos.x != mPos.x
+				|| pos.y != mPos.y
+				|| pos.scale != mPos.scale
+				|| pos.angle != mPos.angle
+				|| pos.tilt != mPos.tilt);
 
-		pos.angle = mRotation;
-		pos.tilt = mTilt;
+		pos.angle = mPos.angle;
+		pos.tilt = mPos.tilt;
 
-		pos.x = mAbsX;
-		pos.y = mAbsY;
-		pos.scale = mAbsScale;
+		pos.x = mPos.x;
+		pos.y = mPos.y;
+		pos.scale = mPos.scale;
 
-		// for tiling
+		// handy for tiling
 		pos.zoomLevel = z;
 
 		return changed;
@@ -212,7 +183,7 @@ public class Viewport {
 			ua = 1;
 		else {
 			// tilt of the plane (center is kept on x = 0)
-			double t = Math.toRadians(mTilt);
+			double t = Math.toRadians(mPos.tilt);
 			double px = y * Math.sin(t);
 			double py = y * Math.cos(t);
 			ua = 1 + (px * ry) / (py * cx);
@@ -240,8 +211,8 @@ public class Viewport {
 
 	/** @return the current center point of the MapView. */
 	public synchronized GeoPoint getMapCenter() {
-		return new GeoPoint(MercatorProjection.toLatitude(mAbsY),
-				MercatorProjection.toLongitude(mAbsX));
+		return new GeoPoint(MercatorProjection.toLatitude(mPos.y),
+				MercatorProjection.toLongitude(mPos.x));
 	}
 
 	/**
@@ -285,10 +256,15 @@ public class Viewport {
 			box.maxY = Math.max(box.maxY, coords[i + 1]);
 		}
 
-		box.minX = (mCurX + box.minX) / mCurScale;
-		box.maxX = (mCurX + box.maxX) / mCurScale;
-		box.minY = (mCurY + box.minY) / mCurScale;
-		box.maxY = (mCurY + box.maxY) / mCurScale;
+		//updatePosition();
+		double cs = mPos.scale * Tile.SIZE;
+		double cx = mPos.x * cs;
+		double cy = mPos.y * cs;
+
+		box.minX = (cx + box.minX) / cs;
+		box.maxX = (cx + box.maxX) / cs;
+		box.minY = (cy + box.minY) / cs;
+		box.maxY = (cy + box.maxY) / cs;
 	}
 
 	/**
@@ -311,8 +287,8 @@ public class Viewport {
 		out.y = mu[1];
 
 		if (scale != 0) {
-			out.x *= scale / mAbsScale;
-			out.y *= scale / mAbsScale;
+			out.x *= scale / mPos.scale;
+			out.y *= scale / mPos.scale;
 		}
 	}
 
@@ -344,11 +320,15 @@ public class Viewport {
 
 		unproject(-mx, my, getZ(-my), mu, 0);
 
-		double dx = mCurX + mu[0];
-		double dy = mCurY + mu[1];
+		double cs = mPos.scale * Tile.SIZE;
+		double cx = mPos.x * cs;
+		double cy = mPos.y * cs;
 
-		dx /= mCurScale;
-		dy /= mCurScale;
+		double dx = cx + mu[0];
+		double dy = cy + mu[1];
+
+		dx /= cs;
+		dy /= cs;
 
 		if (dx > 1) {
 			while (dx > 1)
@@ -386,8 +366,13 @@ public class Viewport {
 	 */
 	public synchronized void project(double x, double y, Point out) {
 
-		mv[0] = (float) (x * mCurScale - mCurX);
-		mv[1] = (float) (y * mCurScale - mCurY);
+		//updatePosition();
+		double cs = mPos.scale * Tile.SIZE;
+		double cx = mPos.x * cs;
+		double cy = mPos.y * cs;
+
+		mv[0] = (float) (x * cs - cx);
+		mv[1] = (float) (y * cs - cy);
 
 		mv[2] = 0;
 		mv[3] = 1;
@@ -408,15 +393,15 @@ public class Viewport {
 		// 4. translate to VIEW_DISTANCE
 		// 5. apply projection
 
-		while (mRotation > 360)
-			mRotation -= 360;
-		while (mRotation < 0)
-			mRotation += 360;
+		while (mPos.angle > 360)
+			mPos.angle -= 360;
+		while (mPos.angle < 0)
+			mPos.angle += 360;
 
-		mRotMatrix.setRotation(mRotation, 0, 0, 1);
+		mRotMatrix.setRotation(mPos.angle, 0, 0, 1);
 
 		// tilt map
-		mTmpMatrix.setRotation(mTilt, 1, 0, 0);
+		mTmpMatrix.setRotation(mPos.tilt, 1, 0, 0);
 
 		// apply first rotation, then tilt
 		mRotMatrix.multiplyMM(mTmpMatrix, mRotMatrix);
@@ -450,35 +435,42 @@ public class Viewport {
 	 * @param my the amount of pixels to move the map vertically.
 	 */
 	public synchronized void moveMap(float mx, float my) {
-		// stop animation
-		animCancel();
-
 		Point p = applyRotation(mx, my);
-		move(p.x, p.y);
+		double tileScale = mPos.scale * Tile.SIZE;
+
+		moveBy(p.x / tileScale, p.y / tileScale);
 	}
 
-	private synchronized void move(double mx, double my) {
-		mAbsX = (mCurX - mx) / mCurScale;
-		mAbsY = (mCurY - my) / mCurScale;
+	void moveInternal(double mx, double my){
+		Point p = applyRotation(mx, my);
+		moveBy(p.x, p.y);
+	}
+
+	void moveBy(double mx, double my) {
+		mPos.x -= mx;
+		mPos.y -= my;
 
 		// clamp latitude
-		mAbsY = FastMath.clamp(mAbsY, 0, 1);
+		mPos.y = FastMath.clamp(mPos.y, 0, 1);
 
 		// wrap longitude
-		while (mAbsX > 1)
-			mAbsX -= 1;
-		while (mAbsX < 0)
-			mAbsX += 1;
-
-		updatePosition();
+		while (mPos.x > 1)
+			mPos.x -= 1;
+		while (mPos.x < 0)
+			mPos.x += 1;
 	}
 
-	private Point applyRotation(float mx, float my) {
-		double rad = Math.toRadians(mRotation);
-		double rcos = Math.cos(rad);
-		double rsin = Math.sin(rad);
-		mMovePoint.x = mx * rcos + my * rsin;
-		mMovePoint.y = mx * -rsin + my * rcos;
+	Point applyRotation(double mx, double my) {
+		if (mPos.angle == 0) {
+			mMovePoint.x = mx;
+			mMovePoint.y = my;
+		} else {
+			double rad = Math.toRadians(mPos.angle);
+			double rcos = Math.cos(rad);
+			double rsin = Math.sin(rad);
+			mMovePoint.x = mx * rcos + my * rsin;
+			mMovePoint.y = mx * -rsin + my * rcos;
+		}
 		return mMovePoint;
 	}
 
@@ -489,28 +481,25 @@ public class Viewport {
 	 * @return true if scale was changed
 	 */
 	public synchronized boolean scaleMap(float scale, float pivotX, float pivotY) {
-		// stop animation
-		animCancel();
-
 		// just sanitize input
-		scale = FastMath.clamp(scale, 0.5f, 2);
+		//scale = FastMath.clamp(scale, 0.5f, 2);
+		if (scale < 0.000001)
+			return false;
 
-		double newScale = mAbsScale * scale;
+		double newScale = mPos.scale * scale;
 
 		newScale = FastMath.clamp(newScale, MIN_SCALE, MAX_SCALE);
 
-		if (newScale == mAbsScale)
+		if (newScale == mPos.scale)
 			return false;
 
-		scale = (float) (newScale / mAbsScale);
+		scale = (float) (newScale / mPos.scale);
 
-		mAbsScale = newScale;
+		mPos.scale = newScale;
 
 		if (pivotX != 0 || pivotY != 0)
 			moveMap(pivotX * (1.0f - scale),
 					pivotY * (1.0f - scale));
-		else
-			updatePosition();
 
 		return true;
 	}
@@ -519,341 +508,64 @@ public class Viewport {
 	 * rotate map around pivot cx,cy
 	 *
 	 * @param radians ...
-	 * @param cx ...
-	 * @param cy ...
+	 * @param pivotX ...
+	 * @param pivotY ...
 	 */
-	public synchronized void rotateMap(double radians, float cx, float cy) {
+	public synchronized void rotateMap(double radians, float pivotX, float pivotY) {
 
 		double rsin = Math.sin(radians);
 		double rcos = Math.cos(radians);
 
-		float x = (float) (cx * rcos + cy * -rsin - cx);
-		float y = (float) (cx * rsin + cy * rcos - cy);
+		float x = (float) (pivotX * rcos + pivotY * -rsin - pivotX);
+		float y = (float) (pivotX * rsin + pivotY * rcos - pivotY);
 
 		moveMap(x, y);
 
-		mRotation += Math.toDegrees(radians);
+		mPos.angle += Math.toDegrees(radians);
 
 		updateMatrix();
 	}
 
 	public synchronized void setRotation(float f) {
 
-		mRotation = f;
+		mPos.angle = f;
 		updateMatrix();
 	}
 
 	public synchronized boolean tiltMap(float move) {
-		return setTilt(mTilt + move);
+		return setTilt(mPos.tilt + move);
 	}
 
 	public synchronized boolean setTilt(float tilt) {
 		tilt = FastMath.clamp(tilt, 0, MAX_TILT);
-		if (tilt == mTilt)
+		if (tilt == mPos.tilt)
 			return false;
-		mTilt = tilt;
+		mPos.tilt = tilt;
 		updateMatrix();
 		return true;
 	}
 
 	public synchronized float getTilt() {
-		return mTilt;
+		return mPos.tilt;
 	}
 
 	private void setMapCenter(double latitude, double longitude) {
 		latitude = MercatorProjection.limitLatitude(latitude);
 		longitude = MercatorProjection.limitLongitude(longitude);
-		mAbsX = MercatorProjection.longitudeToX(longitude);
-		mAbsY = MercatorProjection.latitudeToY(latitude);
+		mPos.x = MercatorProjection.longitudeToX(longitude);
+		mPos.y = MercatorProjection.latitudeToY(latitude);
 	}
 
 	public synchronized void setMapPosition(MapPosition mapPosition) {
-		mAbsScale = FastMath.clamp(mapPosition.scale, MIN_SCALE, MAX_SCALE);
-		mAbsX = mapPosition.x;
-		mAbsY = mapPosition.y;
-		mTilt = mapPosition.tilt;
-		mRotation = mapPosition.angle;
-		updatePosition();
+		mPos.scale = FastMath.clamp(mapPosition.scale, MIN_SCALE, MAX_SCALE);
+		mPos.x = mapPosition.x;
+		mPos.y = mapPosition.y;
+		mPos.tilt = mapPosition.tilt;
+		mPos.angle = mapPosition.angle;
 		updateMatrix();
 	}
 
 	synchronized void setMapCenter(GeoPoint geoPoint) {
 		setMapCenter(geoPoint.getLatitude(), geoPoint.getLongitude());
-		updatePosition();
-	}
-
-	/************************************************************************/
-
-	// TODO move to MapAnimator
-	class AnimState {
-
-	}
-
-	private double mScrollX;
-	private double mScrollY;
-	private double mStartX;
-	private double mStartY;
-	private double mEndX;
-	private double mEndY;
-
-	private double mStartScale;
-	private double mEndScale;
-	private float mStartRotation;
-
-	private float mDuration = 500;
-	private long mAnimEnd = -1;
-
-	private boolean mAnimMove;
-	private boolean mAnimFling;
-	private boolean mAnimScale;
-	private boolean mAnimPivot;
-	private GeoPoint mEndPos;
-	private double mFinalScale;
-
-	public synchronized void animateTo(BoundingBox bbox) {
-		// calculate the maximum scale at which the bbox is completely visible
-		double dx = Math.abs(MercatorProjection.longitudeToX(bbox.getMaxLongitude())
-				- MercatorProjection.longitudeToX(bbox.getMinLongitude()));
-
-		double dy = Math.abs(MercatorProjection.latitudeToY(bbox.getMinLatitude())
-				- MercatorProjection.latitudeToY(bbox.getMaxLatitude()));
-
-		double zx = mWidth / (dx * Tile.SIZE);
-		double zy = mHeight / (dy * Tile.SIZE);
-		double newScale = Math.min(zx, zy);
-
-		//Log.d(TAG, "scale to " + bbox + " " + newScale + " " + mAbsScale
-		//		+ " " + FastMath.log2((int) newScale));
-
-		animateTo(500, bbox.getCenterPoint(), newScale, false);
-	}
-
-	public synchronized void animateTo(long duration, GeoPoint geoPoint, double scale,
-			boolean relative) {
-
-		if (relative) {
-			if (mAnimEnd > 0 && mAnimScale)
-				scale = mFinalScale * scale;
-			else
-				scale = mAbsScale * scale;
-		}
-
-		scale = FastMath.clamp(scale, MIN_SCALE, MAX_SCALE);
-		mFinalScale = scale;
-
-		scale = (float) (scale / mAbsScale);
-
-		mEndScale = mAbsScale * scale - mAbsScale;
-		mStartScale = mAbsScale;
-		mStartRotation = mRotation;
-
-		mStartX = mAbsX;
-		mStartY = mAbsY;
-
-		mEndX = MercatorProjection.longitudeToX(geoPoint.getLongitude());
-		mEndY = MercatorProjection.latitudeToY(geoPoint.getLatitude());
-		mEndX -= mStartX;
-		mEndY -= mStartY;
-		mAnimMove = true;
-		mAnimScale = true;
-		mAnimFling = false;
-
-		mEndPos = geoPoint;
-
-		animStart(duration);
-	}
-
-	public synchronized void animateZoom(long duration, double scale, double pivotX, double pivotY) {
-
-		if (mAnimEnd > 0 && mAnimScale)
-			scale = mFinalScale * scale;
-		else
-			scale = mAbsScale * scale;
-
-		scale = FastMath.clamp(scale, MIN_SCALE, MAX_SCALE);
-		mFinalScale = scale;
-
-		scale = (float) (scale / mAbsScale);
-
-		mEndScale = mAbsScale * scale - mAbsScale;
-		mStartScale = mAbsScale;
-		mStartRotation = mRotation;
-
-		mScrollX = pivotX;
-		mScrollY = pivotY;
-
-		mAnimScale = true;
-		mAnimPivot = (pivotX != 0 || pivotY != 0);
-
-		mAnimFling = false;
-		mAnimMove = false;
-
-		animStart(duration);
-	}
-
-	public synchronized void animateTo(GeoPoint geoPoint) {
-		animateTo(300, geoPoint, 1, true);
-	}
-
-	private void animStart(float duration) {
-		mDuration = duration;
-
-		mAnimEnd = System.currentTimeMillis() + (long) duration;
-		mMap.render();
-	}
-
-	private void animCancel() {
-		mAnimEnd = -1;
-		mEndPos = null;
-		mAnimScale = false;
-		mAnimFling = false;
-		mAnimMove = false;
-		mAnimPivot = false;
-	}
-
-	synchronized boolean fling(float adv) {
-
-		adv = (float) Math.sqrt(adv);
-
-		float dx = mVelocityX * adv;
-		float dy = mVelocityY * adv;
-
-		if (dx != 0 || dy != 0) {
-			Point p = applyRotation((float) (dx - mScrollX), (float) (dy - mScrollY));
-			move(p.x, p.y);
-
-			mScrollX = dx;
-			mScrollY = dy;
-		}
-		return true;
-	}
-
-	private float mVelocityX;
-	private float mVelocityY;
-
-	public synchronized void animateFling(int velocityX, int velocityY,
-			int minX, int maxX, int minY, int maxY) {
-
-		if (velocityX * velocityX + velocityY * velocityY < 2048)
-			return;
-
-		mScrollX = 0;
-		mScrollY = 0;
-
-		float duration = 500;
-
-		// pi times thumb..
-		float flingFactor = (duration / 2500);
-		mVelocityX = velocityX * flingFactor;
-		mVelocityY = velocityY * flingFactor;
-		FastMath.clamp(mVelocityX, minX, maxX);
-		FastMath.clamp(mVelocityY, minY, maxY);
-
-		mAnimFling = true;
-		mAnimMove = false;
-		mAnimScale = false;
-		animStart(duration);
-	}
-
-	/**
-	 * called by GLRenderer at begin of each frame.
-	 */
-	public void updateAnimation() {
-		if (mAnimEnd < 0)
-			return;
-
-		long millisLeft = mAnimEnd - System.currentTimeMillis();
-
-		if (millisLeft <= 0) {
-			// set final position
-			if (mAnimMove) {
-				if (mEndPos == null)
-					doMove(mStartX + mEndX, mStartY + mEndY);
-				else {
-					setMapCenter(mEndPos);
-					mEndPos = null;
-				}
-			}
-
-			if (mAnimScale) {
-				doScale(mFinalScale);
-			}
-
-			updatePosition();
-			mMap.updateMap(true);
-
-			animCancel();
-			return;
-		}
-
-		boolean changed = false;
-
-		float adv = (1.0f - millisLeft / mDuration);
-
-		if (mAnimScale) {
-			if (mEndScale > 0)
-				doScale(mStartScale + (mEndScale * (Math.pow(2, adv) - 1)));
-			else
-				doScale(mStartScale + (mEndScale * adv));
-
-			changed = true;
-		}
-
-		if (mAnimMove) {
-			doMove(mStartX + mEndX * adv, mStartY + mEndY * adv);
-			changed = true;
-		}
-
-		if (mAnimMove && mAnimScale) {
-			mRotation = mStartRotation * (1 - adv);
-			updateMatrix();
-		}
-
-		if (changed) {
-			updatePosition();
-		}
-
-		if (mAnimFling && fling(adv))
-			changed = true;
-
-		// continue animation
-		if (changed) {
-			// inform other layers that position has changed
-			mMap.updateMap(true);
-		} else {
-			// just render next frame
-			mMap.render();
-		}
-	}
-
-	private void doScale(double newScale) {
-		double scale = mAbsScale;
-
-		mAbsScale = newScale;
-
-		if (mAnimPivot) {
-			scale = mAbsScale / scale;
-
-			Point p = applyRotation(
-					(float) (mScrollX * (1.0 - scale)),
-					(float) (mScrollY * (1.0 - scale)));
-			move(p.x, p.y);
-		}
-	}
-
-	private void doMove(double x, double y) {
-		mAbsX = x;
-		mAbsY = y;
-
-		// clamp latitude
-		mAbsY = FastMath.clamp(mAbsY, 0, 1);
-
-		// wrap longitude
-		while (mAbsX > 1)
-			mAbsX -= 1;
-		while (mAbsX < 0)
-			mAbsX += 1;
-
-		updatePosition();
 	}
 }