
org.oscim.map.Animator Maven / Gradle / Ivy
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016 Stephan Leuschner
* Copyright 2016-2018 devemux86
* Copyright 2016 Izumi Kawashima
* Copyright 2017 Wolfgang Schramm
* Copyright 2018 Gustl22
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* 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 .
*/
package org.oscim.map;
import org.oscim.backend.CanvasAdapter;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.core.Point;
import org.oscim.core.Tile;
import org.oscim.renderer.MapRenderer;
import org.oscim.utils.ThreadUtils;
import org.oscim.utils.animation.Easing;
import org.oscim.utils.async.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.oscim.core.MercatorProjection.latitudeToY;
import static org.oscim.core.MercatorProjection.longitudeToX;
import static org.oscim.utils.FastMath.clamp;
public class Animator {
static final Logger log = LoggerFactory.getLogger(Animator.class);
public static final int ANIM_NONE = 0;
public static final int ANIM_MOVE = 1 << 0;
public static final int ANIM_SCALE = 1 << 1;
public static final int ANIM_ROTATE = 1 << 2;
public static final int ANIM_TILT = 1 << 3;
public static final int ANIM_FLING = 1 << 4;
final Map mMap;
final MapPosition mCurPos = new MapPosition();
final MapPosition mStartPos = new MapPosition();
final MapPosition mDeltaPos = new MapPosition();
private final Point mScroll = new Point();
final Point mPivot = new Point();
private final Point mVelocity = new Point();
float mDuration = 500;
long mAnimEnd = -1;
Easing.Type mEasingType = Easing.Type.LINEAR;
int mState = ANIM_NONE;
public Animator(Map map) {
mMap = map;
}
public synchronized void animateTo(BoundingBox bbox) {
animateTo(1000, bbox);
}
public synchronized void animateTo(long duration, BoundingBox bbox) {
animateTo(duration, bbox, Easing.Type.LINEAR);
}
public synchronized void animateTo(long duration, BoundingBox bbox, Easing.Type easingType) {
animateTo(duration, bbox, easingType, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT);
}
public synchronized void animateTo(long duration, BoundingBox bbox, Easing.Type easingType, int state) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mStartPos);
/* TODO for large distance first scale out, then in
* calculate the maximum scale at which the BoundingBox
* is completely visible */
double dx = Math.abs(longitudeToX(bbox.getMaxLongitude())
- longitudeToX(bbox.getMinLongitude()));
double dy = Math.abs(latitudeToY(bbox.getMinLatitude())
- latitudeToY(bbox.getMaxLatitude()));
log.debug("anim bbox " + bbox);
double zx = mMap.getWidth() / (dx * Tile.SIZE);
double zy = mMap.getHeight() / (dy * Tile.SIZE);
double newScale = Math.min(zx, zy);
GeoPoint p = bbox.getCenterPoint();
mDeltaPos.set(longitudeToX(p.getLongitude()) - mStartPos.x,
latitudeToY(p.getLatitude()) - mStartPos.y,
newScale - mStartPos.scale,
-mStartPos.bearing,
-mStartPos.tilt);
animStart(duration, state, easingType);
}
public void animateTo(GeoPoint p) {
animateTo(500, p);
}
public void animateTo(long duration, GeoPoint p) {
animateTo(duration, p, 1, true);
}
/**
* Animate to GeoPoint
*
* @param duration in ms
* @param geoPoint
* @param scale
* @param relative alter scale relative to current scale
*/
public void animateTo(long duration, GeoPoint geoPoint,
double scale, boolean relative) {
animateTo(duration, geoPoint, scale, relative, Easing.Type.LINEAR);
}
/**
* Animate to GeoPoint
*
* @param duration in ms
* @param geoPoint
* @param scale
* @param relative alter scale relative to current scale
* @param easingType easing function
*/
public void animateTo(long duration, GeoPoint geoPoint,
double scale, boolean relative, Easing.Type easingType) {
animateTo(duration, geoPoint, scale, relative, easingType, ANIM_MOVE | ANIM_SCALE);
}
/**
* Animate to GeoPoint
*
* @param duration in ms
* @param geoPoint
* @param scale
* @param relative alter scale relative to current scale
* @param easingType easing function
* @param state animation state
*/
public void animateTo(long duration, GeoPoint geoPoint,
double scale, boolean relative, Easing.Type easingType, int state) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mStartPos);
if (relative)
scale = mStartPos.scale * scale;
scale = mMap.viewport().limitScale(scale);
mDeltaPos.set(longitudeToX(geoPoint.getLongitude()) - mStartPos.x,
latitudeToY(geoPoint.getLatitude()) - mStartPos.y,
scale - mStartPos.scale,
0, 0);
animStart(duration, state, easingType);
}
public void animateTo(MapPosition pos) {
animateTo(500, pos);
}
public void animateTo(long duration, MapPosition pos) {
animateTo(duration, pos, Easing.Type.LINEAR);
}
public void animateTo(long duration, MapPosition pos, Easing.Type easingType) {
animateTo(duration, pos, easingType, ANIM_MOVE | ANIM_SCALE | ANIM_ROTATE | ANIM_TILT);
}
public void animateTo(long duration, MapPosition pos, Easing.Type easingType, int state) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mStartPos);
pos.scale = mMap.viewport().limitScale(pos.scale);
mDeltaPos.set(pos.x - mStartPos.x,
pos.y - mStartPos.y,
pos.scale - mStartPos.scale,
pos.bearing - mStartPos.bearing,
mMap.viewport().limitTilt(pos.tilt) - mStartPos.tilt);
animStart(duration, state, easingType);
}
public void animateZoom(long duration, double scaleBy,
float pivotX, float pivotY) {
animateZoom(duration, scaleBy, pivotX, pivotY, Easing.Type.LINEAR);
}
public void animateZoom(long duration, double scaleBy,
float pivotX, float pivotY, Easing.Type easingType) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mCurPos);
if (mState == ANIM_SCALE)
scaleBy = (mStartPos.scale + mDeltaPos.scale) * scaleBy;
else
scaleBy = mCurPos.scale * scaleBy;
mStartPos.copy(mCurPos);
scaleBy = mMap.viewport().limitScale(scaleBy);
if (scaleBy == 0.0)
return;
mDeltaPos.scale = scaleBy - mStartPos.scale;
mPivot.x = pivotX;
mPivot.y = pivotY;
animStart(duration, ANIM_SCALE, easingType);
}
public void animateFling(float velocityX, float velocityY,
int xmin, int xmax, int ymin, int ymax) {
ThreadUtils.assertMainThread();
if (velocityX * velocityX + velocityY * velocityY < 2048)
return;
mMap.getMapPosition(mStartPos);
mScroll.x = 0;
mScroll.y = 0;
float duration = 500;
float flingFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
mVelocity.x = velocityX * flingFactor;
mVelocity.y = velocityY * flingFactor;
mVelocity.x = clamp(mVelocity.x, xmin, xmax);
mVelocity.y = clamp(mVelocity.y, ymin, ymax);
if (Double.isNaN(mVelocity.x) || Double.isNaN(mVelocity.y)) {
log.debug("fling NaN!");
return;
}
animStart(duration, ANIM_FLING, Easing.Type.SINE_OUT);
}
void animStart(float duration, int state, Easing.Type easingType) {
if (!isActive())
mMap.events.fire(Map.ANIM_START, mMap.mMapPosition);
mCurPos.copy(mStartPos);
mState = state;
mDuration = duration;
mAnimEnd = System.currentTimeMillis() + (long) duration;
mEasingType = easingType;
mMap.render();
}
/**
* called by MapRenderer at begin of each frame.
*/
void updateAnimation() {
if (mState == ANIM_NONE)
return;
long millisLeft = mAnimEnd - MapRenderer.frametime;
ViewController v = mMap.viewport();
/* cancel animation when position was changed since last
* update, i.e. when it was modified outside the animator. */
if (v.getMapPosition(mCurPos)) {
log.debug("cancel anim - changed");
cancel();
return;
}
float adv = clamp(1.0f - millisLeft / mDuration, 1E-6f, 1);
// Avoid redundant calculations in case of linear easing
if (mEasingType != Easing.Type.LINEAR) {
adv = Easing.ease(0, (long) (adv * Long.MAX_VALUE), Long.MAX_VALUE, mEasingType);
adv = clamp(adv, 0, 1);
}
double scaleAdv = 1;
if ((mState & ANIM_SCALE) != 0) {
scaleAdv = doScale(v, adv);
}
if ((mState & ANIM_MOVE) != 0) {
v.moveTo(mStartPos.x + mDeltaPos.x * (adv / scaleAdv),
mStartPos.y + mDeltaPos.y * (adv / scaleAdv));
}
if ((mState & ANIM_FLING) != 0) {
adv = (float) Math.sqrt(adv);
double dx = mVelocity.x * adv;
double dy = mVelocity.y * adv;
if ((dx - mScroll.x) != 0 || (dy - mScroll.y) != 0) {
v.moveMap((float) (dx - mScroll.x),
(float) (dy - mScroll.y));
mScroll.x = dx;
mScroll.y = dy;
}
}
if ((mState & ANIM_ROTATE) != 0) {
v.setRotation(mStartPos.bearing + mDeltaPos.bearing * adv);
}
if ((mState & ANIM_TILT) != 0) {
v.setTilt(mStartPos.tilt + mDeltaPos.tilt * adv);
}
if (millisLeft <= 0) {
//log.debug("animate END");
cancel();
}
/* remember current map position */
final boolean changed = v.getMapPosition(mCurPos);
if (changed) {
mMap.updateMap(true);
} else {
mMap.postDelayed(updateTask, 10);
}
}
Task updateTask = new Task() {
@Override
public int go(boolean canceled) {
if (!canceled)
updateAnimation();
return Task.DONE;
}
};
double doScale(ViewController v, float adv) {
double newScale = mStartPos.scale + mDeltaPos.scale * Math.sqrt(adv);
v.scaleMap((float) (newScale / mCurPos.scale),
(float) mPivot.x, (float) mPivot.y);
return newScale / (mStartPos.scale + mDeltaPos.scale);
}
public void cancel() {
//ThreadUtils.assertMainThread();
mState = ANIM_NONE;
mPivot.x = 0;
mPivot.y = 0;
mMap.events.fire(Map.ANIM_END, mMap.mMapPosition);
}
public boolean isActive() {
return mState != ANIM_NONE;
}
/**
* Get the map position at animation end.
* Note: valid only with animateTo methods.
*/
public MapPosition getEndPosition() {
return mDeltaPos;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy