916 lines
22 KiB
Java
916 lines
22 KiB
Java
/*
|
|
* Copyright 2010, 2011, 2012 mapsforge.org
|
|
* Copyright 2012 Hannes Janetzek
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under the
|
|
* terms of the GNU Lesser General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.oscim.view;
|
|
|
|
import java.lang.ref.WeakReference;
|
|
|
|
import org.oscim.core.BoundingBox;
|
|
import org.oscim.core.GeoPoint;
|
|
import org.oscim.core.MapPosition;
|
|
import org.oscim.core.MercatorProjection;
|
|
import org.oscim.core.PointD;
|
|
import org.oscim.core.PointF;
|
|
import org.oscim.core.Tile;
|
|
import org.oscim.utils.FastMath;
|
|
import org.oscim.utils.Interpolation;
|
|
import org.oscim.utils.Matrix4;
|
|
|
|
import android.opengl.Matrix;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.SystemClock;
|
|
import android.util.Log;
|
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
|
|
|
/**
|
|
* A MapPosition stores the latitude and longitude coordinate of a MapView
|
|
* together with its zoom level, rotation and tilt
|
|
*/
|
|
|
|
public class MapViewPosition {
|
|
private static final String TAG = MapViewPosition.class.getName();
|
|
|
|
public final static int MAX_ZOOMLEVEL = 17;
|
|
public final static int MIN_ZOOMLEVEL = 2;
|
|
public final static int MAX_END_SCALE = 8;
|
|
public final static double MAX_SCALE = ((1 << MAX_ZOOMLEVEL) * MAX_END_SCALE);
|
|
public final static double MIN_SCALE = (1 << MIN_ZOOMLEVEL);
|
|
|
|
// needs to fit for int: 2 * 20 * Tile.TILE_SIZE
|
|
public final static int ABS_ZOOMLEVEL = 20;
|
|
|
|
private final static float MAX_ANGLE = 65;
|
|
|
|
private final MapView mMapView;
|
|
|
|
private double mLatitude;
|
|
private double mLongitude;
|
|
|
|
private double mAbsScale;
|
|
private double mAbsX;
|
|
private double mAbsY;
|
|
|
|
// mAbsScale * Tile.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;
|
|
public float mTilt;
|
|
|
|
private final AnimationHandler mHandler;
|
|
|
|
MapViewPosition(MapView mapView) {
|
|
mMapView = mapView;
|
|
mLatitude = Double.NaN;
|
|
mLongitude = Double.NaN;
|
|
mRotation = 0.0f;
|
|
mTilt = 0;
|
|
mAbsScale = 1;
|
|
|
|
mHandler = new AnimationHandler(this);
|
|
}
|
|
|
|
private final Matrix4 mProjMatrix = new Matrix4();
|
|
private final Matrix4 mProjMatrixI = new Matrix4();
|
|
private final Matrix4 mRotMatrix = new Matrix4();
|
|
private final Matrix4 mViewMatrix = new Matrix4();
|
|
private final Matrix4 mVPMatrix = new Matrix4();
|
|
private final Matrix4 mUnprojMatrix = new Matrix4();
|
|
private final Matrix4 mTmpMatrix = new Matrix4();
|
|
|
|
// temporary vars: only use in synchronized functions!
|
|
private final PointD mMovePoint = new PointD();
|
|
private final float[] mv = new float[4];
|
|
private final float[] mu = new float[4];
|
|
private final float[] mBBoxCoords = new float[8];
|
|
|
|
private float mHeight, mWidth;
|
|
|
|
public final static float VIEW_DISTANCE = 3.0f;
|
|
public final static float VIEW_NEAR = 1;
|
|
public final static float VIEW_FAR = 8;
|
|
// scale map plane at VIEW_DISTANCE to near plane
|
|
public final static float VIEW_SCALE = (VIEW_NEAR / VIEW_DISTANCE) * 0.5f;
|
|
|
|
void setViewport(int width, int height) {
|
|
float s = VIEW_SCALE;
|
|
float aspect = height / (float) width;
|
|
float[] tmp = new float[16];
|
|
|
|
Matrix.frustumM(tmp, 0, -s, s,
|
|
aspect * s, -aspect * s, VIEW_NEAR, VIEW_FAR);
|
|
|
|
mProjMatrix.set(tmp);
|
|
mTmpMatrix.setTranslation(0, 0, -VIEW_DISTANCE);
|
|
mProjMatrix.multiplyMM(mTmpMatrix);
|
|
mProjMatrix.get(tmp);
|
|
|
|
Matrix.invertM(tmp, 0, tmp, 0);
|
|
mProjMatrixI.set(tmp);
|
|
|
|
mHeight = height;
|
|
mWidth = width;
|
|
|
|
updateMatrix();
|
|
}
|
|
|
|
/**
|
|
* Get the current MapPosition
|
|
*
|
|
* @param pos MapPosition object to be updated
|
|
* @return true if current position is different from 'pos'.
|
|
*/
|
|
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));
|
|
|
|
if (pos.lat == mLatitude
|
|
&& pos.lon == mLongitude
|
|
&& pos.zoomLevel == z
|
|
&& pos.scale == scale
|
|
&& pos.angle == mRotation
|
|
&& pos.tilt == mTilt)
|
|
return false;
|
|
|
|
pos.lat = mLatitude;
|
|
pos.lon = mLongitude;
|
|
pos.angle = mRotation;
|
|
pos.tilt = mTilt;
|
|
|
|
// for tiling
|
|
pos.scale = scale;
|
|
pos.zoomLevel = (byte) z;
|
|
|
|
pos.x = mAbsX * (Tile.TILE_SIZE << z);
|
|
pos.y = mAbsY * (Tile.TILE_SIZE << z);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get a copy of current matrices
|
|
*
|
|
* @param view view Matrix
|
|
* @param proj projection Matrix
|
|
* @param vp view and projection
|
|
*/
|
|
public synchronized void getMatrix(Matrix4 view, Matrix4 proj, Matrix4 vp) {
|
|
if (view != null)
|
|
view.copy(mViewMatrix);
|
|
|
|
if (proj != null)
|
|
proj.copy(mProjMatrix);
|
|
|
|
if (vp != null)
|
|
vp.copy(mVPMatrix);
|
|
}
|
|
|
|
/**
|
|
* Get the inverse projection of the viewport, i.e. the
|
|
* coordinates with z==0 that will be projected exactly
|
|
* to screen corners by current view-projection-matrix.
|
|
*
|
|
* @param box float[8] will be set.
|
|
*/
|
|
public synchronized void getMapViewProjection(float[] box) {
|
|
float t = getZ(1);
|
|
float t2 = getZ(-1);
|
|
|
|
// top-right
|
|
unproject(1, -1, t, box, 0);
|
|
// top-left
|
|
unproject(-1, -1, t, box, 2);
|
|
// bottom-left
|
|
unproject(-1, 1, t2, box, 4);
|
|
// bottom-right
|
|
unproject(1, 1, t2, box, 6);
|
|
}
|
|
|
|
/*
|
|
* Get Z-value of the map-plane for a point on screen -
|
|
* calculate the intersection of a ray from camera origin
|
|
* and the map plane
|
|
*/
|
|
private float getZ(float y) {
|
|
// origin is moved by VIEW_DISTANCE
|
|
double cx = VIEW_DISTANCE;
|
|
// 'height' of the ray
|
|
double ry = y * (mHeight / mWidth) * 0.5f;
|
|
|
|
// tilt of the plane (center is kept on x = 0)
|
|
double t = Math.toRadians(mTilt);
|
|
double px = y * Math.sin(t);
|
|
double py = y * Math.cos(t);
|
|
|
|
double ua = 1 + (px * ry) / (py * cx);
|
|
|
|
mv[0] = 0;
|
|
mv[1] = (float) (ry / ua);
|
|
mv[2] = (float) (cx - cx / ua);
|
|
|
|
mProjMatrix.prj(mv);
|
|
|
|
return mv[2];
|
|
}
|
|
|
|
private void unproject(float x, float y, float z, float[] coords, int position) {
|
|
mv[0] = x;
|
|
mv[1] = y;
|
|
mv[2] = z;
|
|
|
|
mUnprojMatrix.prj(mv);
|
|
|
|
coords[position + 0] = mv[0];
|
|
coords[position + 1] = mv[1];
|
|
}
|
|
|
|
/** @return the current center point of the MapView. */
|
|
public synchronized GeoPoint getMapCenter() {
|
|
return new GeoPoint(mLatitude, mLongitude);
|
|
}
|
|
|
|
/**
|
|
* @return a MapPosition or null, if this map position is not valid.
|
|
* @see #isValid()
|
|
*/
|
|
public synchronized MapPosition getMapPosition() {
|
|
if (!isValid()) {
|
|
return null;
|
|
}
|
|
|
|
int z = FastMath.log2((int) mAbsScale);
|
|
z = FastMath.clamp(z, MIN_ZOOMLEVEL, MAX_ZOOMLEVEL);
|
|
float scale = (float) (mAbsScale / (1 << z));
|
|
|
|
return new MapPosition(mLatitude, mLongitude, (byte) z, scale, mRotation);
|
|
}
|
|
|
|
/**
|
|
* ...
|
|
*
|
|
* @return BoundingBox containing view
|
|
*/
|
|
public synchronized BoundingBox getViewBox() {
|
|
|
|
float[] coords = mBBoxCoords;
|
|
|
|
float t = getZ(1);
|
|
float t2 = getZ(-1);
|
|
|
|
unproject(1, -1, t, coords, 0);
|
|
unproject(-1, -1, t, coords, 2);
|
|
unproject(-1, 1, t2, coords, 4);
|
|
unproject(1, 1, t2, coords, 6);
|
|
|
|
double dx, dy;
|
|
double minX = Double.MAX_VALUE;
|
|
double minY = Double.MAX_VALUE;
|
|
|
|
double maxX = Double.MIN_VALUE;
|
|
double maxY = Double.MIN_VALUE;
|
|
|
|
for (int i = 0; i < 8; i += 2) {
|
|
dx = mCurX - coords[i + 0];
|
|
dy = mCurY - coords[i + 1];
|
|
|
|
minX = Math.min(minX, dx);
|
|
maxX = Math.max(maxX, dx);
|
|
minY = Math.min(minY, dy);
|
|
maxY = Math.max(maxY, dy);
|
|
}
|
|
|
|
minX = MercatorProjection.toLongitude(minX, mCurScale);
|
|
maxX = MercatorProjection.toLongitude(maxX, mCurScale);
|
|
minY = MercatorProjection.toLatitude(minY, mCurScale);
|
|
maxY = MercatorProjection.toLatitude(maxY, mCurScale);
|
|
|
|
// yea, this is upside down..
|
|
BoundingBox bbox = new BoundingBox(maxY, minX, minY, maxX);
|
|
|
|
Log.d(TAG, "getScreenBoundingBox " + bbox);
|
|
|
|
return bbox;
|
|
}
|
|
|
|
/**
|
|
* For x, y in screen coordinates set Point to map-tile
|
|
* coordinates at returned scale.
|
|
*
|
|
* @param x screen coordinate
|
|
* @param y screen coordinate
|
|
* @param out Point coords will be set
|
|
* @return current map scale
|
|
*/
|
|
public synchronized float getScreenPointOnMap(float x, float y, PointD out) {
|
|
|
|
// scale to -1..1
|
|
float mx = 1 - (x / mWidth * 2);
|
|
float my = 1 - (y / mHeight * 2);
|
|
|
|
unproject(-mx, my, getZ(-my), mu, 0);
|
|
|
|
out.x = mCurX + mu[0];
|
|
out.y = mCurY + mu[1];
|
|
|
|
return (float) mAbsScale;
|
|
}
|
|
|
|
/**
|
|
* Get the GeoPoint for x,y in screen coordinates.
|
|
* (only used by MapEventsOverlay currently)
|
|
*
|
|
* @param x screen coordinate
|
|
* @param y screen coordinate
|
|
* @return the corresponding GeoPoint
|
|
*/
|
|
public synchronized GeoPoint fromScreenPixels(float x, float y) {
|
|
// scale to -1..1
|
|
float mx = 1 - (x / mWidth * 2);
|
|
float my = 1 - (y / mHeight * 2);
|
|
|
|
unproject(-mx, my, getZ(-my), mu, 0);
|
|
|
|
double dx = mCurX + mu[0];
|
|
double dy = mCurY + mu[1];
|
|
dx /= mAbsScale * Tile.TILE_SIZE;
|
|
dy /= mAbsScale * Tile.TILE_SIZE;
|
|
|
|
if (dx > 1) {
|
|
while (dx > 1)
|
|
dx -= 1;
|
|
} else {
|
|
while (dx < 0)
|
|
dx += 1;
|
|
}
|
|
|
|
if (dy > 1)
|
|
dy = 1;
|
|
else if (dy < 0)
|
|
dy = 0;
|
|
|
|
GeoPoint p = new GeoPoint(
|
|
MercatorProjection.toLatitude(dy),
|
|
MercatorProjection.toLongitude(dx));
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Get the screen pixel for a GeoPoint
|
|
*
|
|
* @param geoPoint the GeoPoint
|
|
* @param out Point projected to screen pixel
|
|
*/
|
|
public synchronized void project(GeoPoint geoPoint, PointF out) {
|
|
|
|
double x = MercatorProjection.longitudeToX(geoPoint.getLongitude()) * mCurScale;
|
|
double y = MercatorProjection.latitudeToY(geoPoint.getLatitude()) * mCurScale;
|
|
|
|
mv[0] = (float) (x - mCurX);
|
|
mv[1] = (float) (y - mCurY);
|
|
|
|
mv[2] = 0;
|
|
mv[3] = 1;
|
|
|
|
mVPMatrix.prj(mv);
|
|
out.x = (int) (mv[0] * (mWidth / 2));
|
|
out.y = (int) -(mv[1] * (mHeight / 2));
|
|
}
|
|
|
|
private void updateMatrix() {
|
|
// --- view matrix
|
|
// 1. scale to window coordinates
|
|
// 2. rotate
|
|
// 3. tilt
|
|
|
|
// --- projection matrix
|
|
// 4. translate to VIEW_DISTANCE
|
|
// 5. apply projection
|
|
|
|
mRotMatrix.setRotation(mRotation, 0, 0, 1);
|
|
|
|
// tilt map
|
|
mTmpMatrix.setRotation(mTilt, 1, 0, 0);
|
|
|
|
// apply first rotation, then tilt
|
|
mRotMatrix.multiplyMM(mTmpMatrix, mRotMatrix);
|
|
|
|
// scale to window coordinates
|
|
mTmpMatrix.setScale(1 / mWidth, 1 / mWidth, 1);
|
|
|
|
mViewMatrix.multiplyMM(mRotMatrix, mTmpMatrix);
|
|
|
|
mVPMatrix.multiplyMM(mProjMatrix, mViewMatrix);
|
|
|
|
//--- unproject matrix:
|
|
|
|
// inverse scale
|
|
mUnprojMatrix.setScale(mWidth, mWidth, 1);
|
|
|
|
// inverse rotation and tilt
|
|
mTmpMatrix.transposeM(mRotMatrix);
|
|
|
|
// (AB)^-1 = B^-1*A^-1, unapply scale, tilt and rotation
|
|
mTmpMatrix.multiplyMM(mUnprojMatrix, mTmpMatrix);
|
|
|
|
// (AB)^-1 = B^-1*A^-1, unapply projection
|
|
mUnprojMatrix.multiplyMM(mTmpMatrix, mProjMatrixI);
|
|
}
|
|
|
|
/** @return true if this MapViewPosition is valid, false otherwise. */
|
|
public synchronized boolean isValid() {
|
|
if (Double.isNaN(mLatitude)) {
|
|
return false;
|
|
} else if (mLatitude < MercatorProjection.LATITUDE_MIN) {
|
|
return false;
|
|
} else if (mLatitude > MercatorProjection.LATITUDE_MAX) {
|
|
return false;
|
|
}
|
|
|
|
if (Double.isNaN(mLongitude)) {
|
|
return false;
|
|
} else if (mLongitude < MercatorProjection.LONGITUDE_MIN) {
|
|
return false;
|
|
} else if (mLongitude > MercatorProjection.LONGITUDE_MAX) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Moves this MapViewPosition by the given amount of pixels.
|
|
*
|
|
* @param mx the amount of pixels to move the map horizontally.
|
|
* @param my the amount of pixels to move the map vertically.
|
|
*/
|
|
public synchronized void moveMap(float mx, float my) {
|
|
// stop animation
|
|
mHandler.cancel();
|
|
|
|
PointD p = applyRotation(mx, my);
|
|
|
|
move(p.x, p.y);
|
|
}
|
|
|
|
private synchronized void move(double mx, double my) {
|
|
mAbsX = (mCurX - mx) / mCurScale;
|
|
mAbsY = (mCurY - my) / mCurScale;
|
|
|
|
// clamp latitude
|
|
mAbsY = FastMath.clamp(mAbsY, 0, 1);
|
|
|
|
// wrap longitude
|
|
while (mAbsX > 1)
|
|
mAbsX -= 1;
|
|
while (mAbsX < 0)
|
|
mAbsX += 1;
|
|
|
|
mLongitude = MercatorProjection.toLongitude(mAbsX);
|
|
mLatitude = MercatorProjection.toLatitude(mAbsY);
|
|
|
|
updatePosition();
|
|
}
|
|
|
|
private synchronized void moveAbs(double x, double y) {
|
|
double f = Tile.TILE_SIZE << ABS_ZOOMLEVEL;
|
|
|
|
mAbsX = x / f;
|
|
mAbsY = y / f;
|
|
|
|
// clamp latitude
|
|
mAbsY = FastMath.clamp(mAbsY, 0, 1);
|
|
|
|
// wrap longitude
|
|
while (mAbsX > 1)
|
|
mAbsX -= 1;
|
|
while (mAbsX < 0)
|
|
mAbsX += 1;
|
|
|
|
mLongitude = MercatorProjection.toLongitude(mAbsX);
|
|
mLatitude = MercatorProjection.toLatitude(mAbsY);
|
|
|
|
updatePosition();
|
|
}
|
|
|
|
private PointD applyRotation(float mx, float my) {
|
|
|
|
if (mMapView.mRotationEnabled || mMapView.mCompassEnabled) {
|
|
double rad = Math.toRadians(mRotation);
|
|
double rcos = Math.cos(rad);
|
|
double rsin = Math.sin(rad);
|
|
float x = (float) (mx * rcos + my * rsin);
|
|
float y = (float) (mx * -rsin + my * rcos);
|
|
mx = x;
|
|
my = y;
|
|
}
|
|
|
|
mMovePoint.x = mx;
|
|
mMovePoint.y = my;
|
|
return mMovePoint;
|
|
}
|
|
|
|
/**
|
|
* @param scale map by this factor
|
|
* @param pivotX ...
|
|
* @param pivotY ...
|
|
* @return true if scale was changed
|
|
*/
|
|
public synchronized boolean scaleMap(float scale, float pivotX, float pivotY) {
|
|
// stop animation
|
|
mHandler.cancel();
|
|
|
|
// just sanitize input
|
|
scale = FastMath.clamp(scale, 0.5f, 2);
|
|
|
|
double newScale = mAbsScale * scale;
|
|
|
|
newScale = FastMath.clamp(newScale, MIN_SCALE, MAX_SCALE);
|
|
|
|
if (newScale == mAbsScale)
|
|
return false;
|
|
|
|
scale = (float) (newScale / mAbsScale);
|
|
|
|
mAbsScale = newScale;
|
|
|
|
if (pivotX != 0 || pivotY != 0)
|
|
moveMap(pivotX * (1.0f - scale),
|
|
pivotY * (1.0f - scale));
|
|
else
|
|
updatePosition();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* rotate map around pivot cx,cy
|
|
*
|
|
* @param radians ...
|
|
* @param cx ...
|
|
* @param cy ...
|
|
*/
|
|
public synchronized void rotateMap(double radians, float cx, float cy) {
|
|
|
|
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);
|
|
|
|
moveMap(x, y);
|
|
|
|
mRotation += Math.toDegrees(radians);
|
|
|
|
updateMatrix();
|
|
}
|
|
|
|
public synchronized void setRotation(float f) {
|
|
mRotation = f;
|
|
updateMatrix();
|
|
}
|
|
|
|
public synchronized boolean tiltMap(float move) {
|
|
float tilt = FastMath.clamp(mTilt + move, 0, MAX_ANGLE);
|
|
|
|
if (mTilt == tilt)
|
|
return false;
|
|
|
|
setTilt(tilt);
|
|
|
|
return true;
|
|
}
|
|
|
|
public synchronized void setTilt(float f) {
|
|
mTilt = f;
|
|
updateMatrix();
|
|
}
|
|
|
|
private void setMapCenter(double latitude, double longitude) {
|
|
mLatitude = MercatorProjection.limitLatitude(latitude);
|
|
mLongitude = MercatorProjection.limitLongitude(longitude);
|
|
mAbsX = MercatorProjection.longitudeToX(mLongitude);
|
|
mAbsY = MercatorProjection.latitudeToY(mLatitude);
|
|
updatePosition();
|
|
}
|
|
|
|
synchronized void setMapCenter(GeoPoint geoPoint) {
|
|
setMapCenter(geoPoint.getLatitude(), geoPoint.getLongitude());
|
|
}
|
|
|
|
synchronized void setMapCenter(MapPosition mapPosition) {
|
|
setZoomLevelLimit(mapPosition.zoomLevel);
|
|
setMapCenter(mapPosition.lat, mapPosition.lon);
|
|
}
|
|
|
|
synchronized void setZoomLevel(byte zoomLevel) {
|
|
setZoomLevelLimit(zoomLevel);
|
|
updatePosition();
|
|
}
|
|
|
|
private void setZoomLevelLimit(byte zoomLevel) {
|
|
mAbsScale = FastMath.clamp(1 << zoomLevel, MIN_SCALE, MAX_SCALE);
|
|
}
|
|
|
|
private void updatePosition() {
|
|
mCurScale = mAbsScale * Tile.TILE_SIZE;
|
|
|
|
mCurX = mAbsX * mCurScale;
|
|
mCurY = mAbsY * mCurScale;
|
|
}
|
|
|
|
/************************************************************************/
|
|
// TODO move to MapAnimator:
|
|
|
|
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 mDuration = 500;
|
|
private final static double LOG4 = Math.log(4);
|
|
|
|
private boolean mAnimMove;
|
|
private boolean mAnimFling;
|
|
private boolean mAnimScale;
|
|
private final AccelerateDecelerateInterpolator mDecInterpolator = new AccelerateDecelerateInterpolator();
|
|
|
|
public synchronized void animateTo(BoundingBox bbox) {
|
|
|
|
// calculate the minimum 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 aspect = (Math.min(mWidth, mHeight) / Tile.TILE_SIZE);
|
|
double z = Math.min(
|
|
-LOG4 * Math.log(dx) + aspect,
|
|
-LOG4 * Math.log(dy) + aspect);
|
|
|
|
double newScale = Math.pow(2, z);
|
|
|
|
newScale = FastMath.clamp(newScale, MIN_SCALE, 1 << ABS_ZOOMLEVEL);
|
|
|
|
float scale = (float) (newScale / mAbsScale);
|
|
|
|
Log.d(TAG, "scale to " + bbox + " " + z + " " + newScale + " " + mAbsScale
|
|
+ " " + FastMath.log2((int) newScale) + " " + scale);
|
|
|
|
mEndScale = mAbsScale * scale - mAbsScale;
|
|
//mEndScale = scale - 1;
|
|
mStartScale = mAbsScale;
|
|
|
|
// reset rotation/tilt
|
|
//mTilt = 0;
|
|
//mRotation = 0;
|
|
//updateMatrix();
|
|
double f = Tile.TILE_SIZE << ABS_ZOOMLEVEL;
|
|
mStartX = mAbsX * f;
|
|
mStartY = mAbsY * f;
|
|
|
|
GeoPoint geoPoint = bbox.getCenterPoint();
|
|
|
|
mEndX = MercatorProjection.longitudeToX(geoPoint.getLongitude()) * f;
|
|
mEndY = MercatorProjection.latitudeToY(geoPoint.getLatitude()) * f;
|
|
mEndX -= mStartX;
|
|
mEndY -= mStartY;
|
|
mAnimMove = true;
|
|
mAnimScale = true;
|
|
mAnimFling = false;
|
|
mDuration = 500;
|
|
|
|
mHandler.start((int) mDuration);
|
|
}
|
|
|
|
public synchronized void animateTo(GeoPoint geoPoint) {
|
|
double f = Tile.TILE_SIZE << ABS_ZOOMLEVEL;
|
|
|
|
mStartX = mAbsX * f;
|
|
mStartY = mAbsY * f;
|
|
|
|
mEndX = MercatorProjection.longitudeToX(geoPoint.getLongitude()) * f;
|
|
mEndY = MercatorProjection.latitudeToY(geoPoint.getLatitude()) * f;
|
|
|
|
mEndX -= mStartX;
|
|
mEndY -= mStartY;
|
|
|
|
mAnimMove = true;
|
|
mAnimScale = false;
|
|
mAnimFling = false;
|
|
|
|
mDuration = 300;
|
|
mHandler.start(mDuration);
|
|
}
|
|
|
|
|
|
synchronized boolean fling(long millisLeft){
|
|
|
|
float delta = (mDuration - millisLeft) / mDuration;
|
|
float adv = Interpolation.exp5Out.apply(delta);
|
|
//adv *= Interpolation.
|
|
//float adv = delta;
|
|
float dx = mVelocityX * adv;
|
|
float dy = mVelocityY * adv;
|
|
|
|
if (dx != 0 || dy != 0){
|
|
moveMap((float)(dx - mScrollX), (float)(dy - mScrollY));
|
|
|
|
mMapView.redrawMap(true);
|
|
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) {
|
|
|
|
mScrollX = 0;
|
|
mScrollY = 0;
|
|
|
|
mDuration = 300;
|
|
|
|
mVelocityX = velocityX * (mDuration / 1000);
|
|
mVelocityY = velocityY * (mDuration / 1000);
|
|
FastMath.clamp(mVelocityX, minX, maxX);
|
|
FastMath.clamp(mVelocityY, minY, maxY);
|
|
|
|
// mScroller.fling(0, 0, velocityX, velocityY, minX, maxX, minY, maxY);
|
|
mAnimFling = true;
|
|
mAnimMove = false;
|
|
mAnimScale = false;
|
|
|
|
//mMapView.mGLView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
|
|
|
mHandler.start(mDuration);
|
|
}
|
|
|
|
public synchronized void animateZoom(float scale) {
|
|
mStartScale = mAbsScale;
|
|
mEndScale = mAbsScale * scale - mAbsScale;
|
|
|
|
//mMapView.mGLView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
|
|
|
mDuration = 300;
|
|
mHandler.start(mDuration);
|
|
}
|
|
|
|
public void updateAnimation() {
|
|
//scroll();
|
|
}
|
|
|
|
void onTick(long millisLeft) {
|
|
boolean changed = false;
|
|
|
|
float adv = (1.0f - millisLeft / mDuration);
|
|
adv = mDecInterpolator.getInterpolation(adv);
|
|
|
|
if (mAnimScale) {
|
|
if (mEndScale > 0)
|
|
// double s = (1 + adv * adv * mEndScale);
|
|
// mAbsScale = mStartScale * s;
|
|
// Log.d(TAG, "scale: " + s + " " + mAbsScale + " " + mStartScale);
|
|
//}
|
|
mAbsScale = mStartScale + (mEndScale * (Math.pow(2, adv) - 1));
|
|
else
|
|
mAbsScale = mStartScale + (mEndScale * adv);
|
|
|
|
changed = true;
|
|
}
|
|
|
|
if (mAnimMove) {
|
|
moveAbs(mStartX + mEndX * adv, mStartY + mEndY * adv);
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
updatePosition();
|
|
}
|
|
|
|
if (mAnimFling && fling(millisLeft))
|
|
changed = true;
|
|
|
|
if (changed)
|
|
mMapView.redrawMap(true);
|
|
}
|
|
|
|
void onFinish() {
|
|
|
|
if (mAnimMove) {
|
|
moveAbs(mStartX + mEndX, mStartY + mEndY);
|
|
}
|
|
|
|
if (mAnimScale) {
|
|
mAbsScale = mStartScale + mEndScale;
|
|
}
|
|
|
|
updatePosition();
|
|
|
|
//mMapView.mGLView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
|
|
|
mMapView.redrawMap(true);
|
|
}
|
|
|
|
/**
|
|
* below is borrowed from CountDownTimer class:
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*/
|
|
static class AnimationHandler extends Handler {
|
|
private final WeakReference<MapViewPosition> mMapViewPosition;
|
|
private static final int MSG = 1;
|
|
|
|
long mMillisInFuture;
|
|
|
|
long mInterval = 16;
|
|
|
|
long mStopTimeInFuture;
|
|
|
|
AnimationHandler(MapViewPosition mapAnimator) {
|
|
mMapViewPosition = new WeakReference<MapViewPosition>(mapAnimator);
|
|
}
|
|
|
|
public synchronized final void start(float millis) {
|
|
mMillisInFuture = (int) millis;
|
|
MapViewPosition animator = mMapViewPosition.get();
|
|
if (animator == null)
|
|
return;
|
|
|
|
if (mMillisInFuture <= 0) {
|
|
animator.onFinish();
|
|
return;
|
|
}
|
|
|
|
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
|
|
removeMessages(MSG);
|
|
sendMessage(obtainMessage(MSG));
|
|
}
|
|
|
|
public final void cancel() {
|
|
removeMessages(MSG);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
MapViewPosition animator = mMapViewPosition.get();
|
|
if (animator == null)
|
|
return;
|
|
|
|
final long millisLeft = mStopTimeInFuture
|
|
- SystemClock.elapsedRealtime();
|
|
|
|
if (millisLeft <= 0) {
|
|
animator.onFinish();
|
|
} else if (millisLeft < mInterval) {
|
|
// no tick, just delay until done
|
|
sendMessageDelayed(obtainMessage(MSG), millisLeft);
|
|
} else {
|
|
long lastTickStart = SystemClock.elapsedRealtime();
|
|
animator.onTick(millisLeft);
|
|
|
|
// take into account user's onTick taking time to
|
|
// execute
|
|
long delay = lastTickStart + mInterval
|
|
- SystemClock.elapsedRealtime();
|
|
|
|
// special case: user's onTick took more than interval
|
|
// to
|
|
// complete, skip to next interval
|
|
while (delay < 0)
|
|
delay += mInterval;
|
|
|
|
sendMessageDelayed(obtainMessage(MSG), delay);
|
|
}
|
|
}
|
|
}
|
|
}
|