package net.osmand.plus.views;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.osmand.LogUtil;
import net.osmand.data.preparation.MapTileDownloader;
import net.osmand.data.preparation.MapTileDownloader.DownloadRequest;
import net.osmand.data.preparation.MapTileDownloader.IMapDownloaderCallback;
import net.osmand.map.IMapLocationListener;
import net.osmand.map.ITileSource;
import net.osmand.osm.LatLon;
import net.osmand.osm.MapUtils;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.ResourceManager;
import net.osmand.plus.activities.OsmandApplication;
import net.osmand.plus.views.AnimateDraggingMapThread.AnimateDraggingCallback;
import net.osmand.plus.views.MultiTouchSupport.MultiTouchZoomListener;
import org.apache.commons.logging.Log;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.SurfaceHolder.Callback;
public class OsmandMapTileView extends SurfaceView implements IMapDownloaderCallback, Callback, AnimateDraggingCallback, OnGestureListener,
OnDoubleTapListener, MultiTouchZoomListener {
public static final int OVERZOOM_IN = 2;
protected final int emptyTileDivisor = 16;
public interface OnTrackBallListener {
public boolean onTrackBallEvent(MotionEvent e);
}
public interface OnLongClickListener {
public boolean onLongPressEvent(PointF point);
}
public interface OnClickListener {
public boolean onPressEvent(PointF point);
}
protected static final Log log = LogUtil.getLog(OsmandMapTileView.class);
/**MapTree
* zoom level - could be float to show zoomed tiles
*/
private float zoom = 3;
private double longitude = 0d;
private double latitude = 0d;
private float rotate = 0;
private float rotateSin = 0;
private float rotateCos = 1;
private int mapPosition;
private boolean showMapPosition = true;
// name of source map
private ITileSource map = null;
private IMapLocationListener locationListener;
private OnLongClickListener onLongClickListener;
private OnClickListener onClickListener;
private OnTrackBallListener trackBallDelegate;
private List<OsmandMapLayer> layers = new ArrayList<OsmandMapLayer>();
private Map<OsmandMapLayer, Float> zOrders = new HashMap<OsmandMapLayer, Float>();
// UI Part
// handler to refresh map (in ui thread - not necessary in ui thread, but msg queue is desirable).
protected Handler handler = new Handler();
private AnimateDraggingMapThread animatedDraggingThread;
private float initialMultiTouchZoom;
private PointF initialMultiTouchCenterPoint;
private LatLon initialMultiTouchLocation;
private GestureDetector gestureDetector;
private MultiTouchSupport multiTouchSupport;
Paint paintGrayFill;
Paint paintBlackFill;
Paint paintWhiteFill;
Paint paintCenter;
Paint paintBitmap;
private DisplayMetrics dm;
private final OsmandApplication application;
public OsmandMapTileView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
application = (OsmandApplication) context.getApplicationContext();
}
public OsmandMapTileView(Context context) {
super(context);
initView();
application = (OsmandApplication) context.getApplicationContext();
}
// ///////////////////////////// INITIALIZING UI PART ///////////////////////////////////
public void initView() {
paintGrayFill = new Paint();
paintGrayFill.setColor(Color.GRAY);
paintGrayFill.setStyle(Style.FILL);
// when map rotate
paintGrayFill.setAntiAlias(true);
paintBlackFill= new Paint();
paintBlackFill.setColor(Color.BLACK);
paintBlackFill.setStyle(Style.FILL);
// when map rotate
paintBlackFill.setAntiAlias(true);
paintWhiteFill = new Paint();
paintWhiteFill.setColor(Color.WHITE);
paintWhiteFill.setStyle(Style.FILL);
// when map rotate
paintWhiteFill.setAntiAlias(true);
paintCenter = new Paint();
paintCenter.setStyle(Style.STROKE);
paintCenter.setColor(Color.rgb(60, 60, 60));
paintCenter.setStrokeWidth(2);
paintCenter.setAntiAlias(true);
paintBitmap = new Paint();
paintBitmap.setFilterBitmap(true);
setClickable(true);
setLongClickable(true);
setFocusable(true);
getHolder().addCallback(this);
animatedDraggingThread = new AnimateDraggingMapThread();
animatedDraggingThread.setCallback(this);
gestureDetector = new GestureDetector(getContext(), this);
multiTouchSupport = new MultiTouchSupport(getContext(), this);
gestureDetector.setOnDoubleTapListener(this);
WindowManager mgr = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
dm = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(dm);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
refreshMap();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
refreshMap();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void addLayer(OsmandMapLayer layer, float zOrder) {
int i = 0;
for (i = 0; i < layers.size(); i++) {
if (zOrders.get(layers.get(i)) > zOrder) {
break;
}
}
layer.initLayer(this);
layers.add(i, layer);
zOrders.put(layer, zOrder);
}
public void removeLayer(OsmandMapLayer layer) {
layers.remove(layer);
zOrders.remove(layer);
layer.destroyLayer();
}
public List<OsmandMapLayer> getLayers() {
return layers;
}
public OsmandApplication getApplication() {
return application;
}
// ///////////////////////// NON UI PART (could be extracted in common) /////////////////////////////
/**
* Returns real tile size in pixels for float zoom .
*/
public float getTileSize() {
float res = map == null ? 256 : map.getTileSize();
if (zoom != (int) zoom) {
res *= (float) Math.pow(2, zoom - (int) zoom);
}
// that trigger allows to scale tiles for certain devices
// for example for device with density > 1 draw tiles the same size as with density = 1
// It makes text bigger but blurry, the settings could be introduced for that
if (dm != null && dm.density > 1f && !OsmandSettings.isUsingHighResMaps(getSettings()) ) {
res *= dm.density;
}
return res;
}
public int getSourceTileSize() {
return map == null ? 256 : map.getTileSize();
}
/**
* @return x tile based on (int) zoom
*/
public float getXTile() {
return (float) MapUtils.getTileNumberX(getZoom(), longitude);
}
/**
* @return y tile based on (int) zoom
*/
public float getYTile() {
return (float) MapUtils.getTileNumberY(getZoom(), latitude);
}
public int getMaximumShownMapZoom(){
if(map == null){
return 21;
} else {
return map.getMaximumZoomSupported() + OVERZOOM_IN;
}
}
public int getMinimumShownMapZoom(){
if(map == null){
return 1;
} else {
return map.getMinimumZoomSupported();
}
}
public void setZoom(float zoom) {
if (zoom <= getMaximumShownMapZoom() && zoom >= getMinimumShownMapZoom()) {
animatedDraggingThread.stopAnimating();
this.zoom = zoom;
refreshMap();
}
}
// for internal usage
@Override
public void zoomTo(float zoom, boolean notify) {
if ((map == null && zoom < 23)
|| (map != null && (map.getMaximumZoomSupported() + OVERZOOM_IN) >= zoom && map.getMinimumZoomSupported() <= zoom)) {
this.zoom = zoom;
refreshMap();
if (notify && locationListener != null) {
locationListener.locationChanged(latitude, longitude, this);
}
}
}
public void setRotate(float rotate) {
float diff = rotate-this.rotate;
if (Math.min(Math.abs((diff+360)%360),Math.abs((diff-360)%360)) > 5) { //check smallest rotation
animatedDraggingThread.startRotate(rotate);
}
}
public boolean isShowMapPosition() {
return showMapPosition;
}
public void setShowMapPosition(boolean showMapPosition) {
this.showMapPosition = showMapPosition;
}
public float getRotate() {
return rotate;
}
public ITileSource getMap() {
return map;
}
public void setMap(ITileSource map) {
this.map = map;
if (map != null && map.getMaximumZoomSupported() + OVERZOOM_IN < this.zoom) {
zoom = map.getMaximumZoomSupported() + OVERZOOM_IN;
}
if (map != null && map.getMinimumZoomSupported() > this.zoom) {
zoom = map.getMinimumZoomSupported();
}
refreshMap();
}
public void setLatLon(double latitude, double longitude) {
animatedDraggingThread.stopAnimating();
this.latitude = latitude;
this.longitude = longitude;
refreshMap();
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
public int getZoom() {
return (int) zoom;
}
public double getRealZoom() {
return zoom;
}
public boolean isZooming(){
return zoom != getZoom();
}
public void setMapLocationListener(IMapLocationListener l) {
locationListener = l;
}
/**
* Adds listener to control when map is dragging
*/
public IMapLocationListener setMapLocationListener() {
return locationListener;
}
// ////////////////////////////// DRAWING MAP PART /////////////////////////////////////////////
protected void drawEmptyTile(Canvas cvs, float x, float y, float ftileSize, boolean nightMode) {
float tileDiv = (ftileSize / emptyTileDivisor);
for (int k1 = 0; k1 < emptyTileDivisor; k1++) {
for (int k2 = 0; k2 < emptyTileDivisor; k2++) {
float xk = x + tileDiv * k1;
float yk = y + tileDiv * k2;
if ((k1 + k2) % 2 == 0) {
cvs.drawRect(xk, yk, xk + tileDiv, yk + tileDiv, paintGrayFill);
} else {
cvs.drawRect(xk, yk, xk + tileDiv, yk + tileDiv, nightMode ? paintBlackFill : paintWhiteFill);
}
}
}
}
public int getCenterPointX() {
return getWidth() / 2;
}
public int getCenterPointY() {
if (mapPosition == OsmandSettings.BOTTOM_CONSTANT) {
return 3 * getHeight() / 4;
}
return getHeight() / 2;
}
public void setMapPosition(int type) {
this.mapPosition = type;
}
private void drawOverMap(Canvas canvas, RectF latlonRect, boolean nightMode) {
int w = getCenterPointX();
int h = getCenterPointY();
canvas.restore();
for (int i = 0; i < layers.size(); i++) {
try {
OsmandMapLayer layer = layers.get(i);
canvas.save();
if (!layer.drawInScreenPixels()) {
canvas.rotate(rotate, w, h);
}
layer.onDraw(canvas, latlonRect, nightMode);
canvas.restore();
} catch (IndexOutOfBoundsException e) {
// skip it
}
}
if (showMapPosition) {
canvas.drawCircle(w, h, 3 * dm.density, paintCenter);
canvas.drawCircle(w, h, 7 * dm.density, paintCenter);
}
}
public void calculateTileRectangle(Rect pixRect, float cx, float cy, float ctilex, float ctiley, RectF tileRect) {
float x1 = calcDiffTileX(pixRect.left - cx, pixRect.top - cy);
float x2 = calcDiffTileX(pixRect.left - cx, pixRect.bottom - cy);
float x3 = calcDiffTileX(pixRect.right - cx, pixRect.top - cy);
float x4 = calcDiffTileX(pixRect.right - cx, pixRect.bottom - cy);
float y1 = calcDiffTileY(pixRect.left - cx, pixRect.top - cy);
float y2 = calcDiffTileY(pixRect.left - cx, pixRect.bottom - cy);
float y3 = calcDiffTileY(pixRect.right - cx, pixRect.top - cy);
float y4 = calcDiffTileY(pixRect.right - cx, pixRect.bottom - cy);
float l = Math.min(Math.min(x1, x2), Math.min(x3, x4)) + ctilex;
float r = Math.max(Math.max(x1, x2), Math.max(x3, x4)) + ctilex;
float t = Math.min(Math.min(y1, y2), Math.min(y3, y4)) + ctiley;
float b = Math.max(Math.max(y1, y2), Math.max(y3, y4)) + ctiley;
tileRect.set(l, t, r, b);
}
public void calculatePixelRectangle(Rect pixelRect, float cx, float cy, float ctilex, float ctiley, RectF tileRect) {
float x1 = calcDiffPixelX(tileRect.left - ctilex, tileRect.top - ctiley);
float x2 = calcDiffPixelX(tileRect.left - ctilex, tileRect.bottom - ctiley);
float x3 = calcDiffPixelX(tileRect.right - ctilex, tileRect.top - ctiley);
float x4 = calcDiffPixelX(tileRect.right - ctilex, tileRect.bottom - ctiley);
float y1 = calcDiffPixelY(tileRect.left - ctilex, tileRect.top - ctiley);
float y2 = calcDiffPixelY(tileRect.left - ctilex, tileRect.bottom - ctiley);
float y3 = calcDiffPixelY(tileRect.right - ctilex, tileRect.top - ctiley);
float y4 = calcDiffPixelY(tileRect.right - ctilex, tileRect.bottom - ctiley);
int l = Math.round(Math.min(Math.min(x1, x2), Math.min(x3, x4)) + cx);
int r = Math.round(Math.max(Math.max(x1, x2), Math.max(x3, x4)) + cx);
int t = Math.round(Math.min(Math.min(y1, y2), Math.min(y3, y4)) + cy);
int b = Math.round(Math.max(Math.max(y1, y2), Math.max(y3, y4)) + cy);
pixelRect.set(l, t, r, b);
}
// used only to save space & reuse
protected RectF tilesRect = new RectF();
protected RectF latlonRect = new RectF();
protected Rect boundsRect = new Rect();
protected RectF bitmapToDraw = new RectF();
protected Rect bitmapToZoom = new Rect();
protected SharedPreferences settings = null;
public SharedPreferences getSettings(){
if(settings == null){
settings = OsmandSettings.getPrefs(getContext());
}
return settings;
}
private void refreshMapInternal() {
if (handler.hasMessages(1)) {
return;
}
boolean useInternet = OsmandSettings.isUsingInternetToDownloadTiles(getSettings());
if (useInternet) {
MapTileDownloader.getInstance().refuseAllPreviousRequests();
}
float ftileSize = getTileSize();
int tileSize = getSourceTileSize();
SurfaceHolder holder = getHolder();
synchronized (holder) {
int nzoom = getZoom();
float tileX = (float) MapUtils.getTileNumberX(nzoom, longitude);
float tileY = (float) MapUtils.getTileNumberY(nzoom, latitude);
float w = getCenterPointX();
float h = getCenterPointY();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
canvas.save();
boolean nightMode = false;
if(application != null){
Boolean dayNightRenderer = application.getDaynightHelper().getDayNightRenderer();
if(dayNightRenderer != null){
nightMode = !dayNightRenderer.booleanValue();
}
}
boundsRect.set(0, 0, getWidth(), getHeight());
canvas.rotate(rotate, w, h);
try {
calculateTileRectangle(boundsRect, w, h, tileX, tileY, tilesRect);
int left = (int) FloatMath.floor(tilesRect.left);
int top = (int) FloatMath.floor(tilesRect.top );
int width = (int) FloatMath.ceil(tilesRect.right - left);
int height = (int) FloatMath.ceil(tilesRect.bottom - top);
latlonRect.top = (float) MapUtils.getLatitudeFromTile(nzoom, tilesRect.top);
latlonRect.left = (float) MapUtils.getLongitudeFromTile(nzoom, tilesRect.left);
latlonRect.bottom = (float) MapUtils.getLatitudeFromTile(nzoom, tilesRect.bottom);
latlonRect.right = (float) MapUtils.getLongitudeFromTile(nzoom, tilesRect.right);
if (map != null) {
ResourceManager mgr = getApplication().getResourceManager();
useInternet = useInternet && OsmandSettings.isInternetConnectionAvailable(getContext())
&& map.couldBeDownloadedFromInternet();
int maxLevel = Math.min(OsmandSettings.getMaximumLevelToDownloadTile(getSettings()), map.getMaximumZoomSupported());
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
int leftPlusI = (int) FloatMath.floor((float)MapUtils.getTileNumberX(nzoom, MapUtils.getLongitudeFromTile(nzoom, left+i)));
int topPlusJ = (int) FloatMath.floor((float)MapUtils.getTileNumberY(nzoom, MapUtils.getLatitudeFromTile(nzoom, top + j)));
float x1 = (left + i - tileX) * ftileSize + w;
float y1 = (top + j - tileY) * ftileSize + h;
String ordImgTile = mgr.calculateTileId(map, leftPlusI, topPlusJ, nzoom);
// asking tile image async
boolean imgExist = mgr.tileExistOnFileSystem(ordImgTile, map, leftPlusI, topPlusJ, nzoom);
Bitmap bmp = null;
boolean originalBeLoaded = useInternet && nzoom <= maxLevel;
if (imgExist || originalBeLoaded) {
bmp = mgr.getTileImageForMapAsync(ordImgTile, map, leftPlusI, topPlusJ, nzoom, useInternet);
}
if (bmp == null) {
int div = 2;
// asking if there is small version of the map (in cache)
String imgTile2 = mgr.calculateTileId(map, leftPlusI / 2, topPlusJ / 2, nzoom - 1);
String imgTile4 = mgr.calculateTileId(map, leftPlusI / 4, topPlusJ / 4, nzoom - 2);
if (originalBeLoaded || imgExist) {
bmp = mgr.getTileImageFromCache(imgTile2);
div = 2;
if (bmp == null) {
bmp = mgr.getTileImageFromCache(imgTile4);
div = 4;
}
}
if (!originalBeLoaded && !imgExist) {
if (mgr.tileExistOnFileSystem(imgTile2, map, leftPlusI / 2, topPlusJ / 2, nzoom - 1)
|| (useInternet && nzoom - 1 <= maxLevel)) {
bmp = mgr.getTileImageForMapAsync(imgTile2, map, leftPlusI / 2, topPlusJ / 2, nzoom - 1,
useInternet);
div = 2;
} else if (mgr.tileExistOnFileSystem(imgTile4, map, leftPlusI / 4, topPlusJ / 4, nzoom - 2)
|| (useInternet && nzoom - 2 <= maxLevel)) {
bmp = mgr.getTileImageForMapAsync(imgTile4, map, leftPlusI / 4, topPlusJ / 4, nzoom - 2,
useInternet);
div = 4;
}
}
if (bmp == null) {
drawEmptyTile(canvas, x1, y1, ftileSize, nightMode);
} else {
int xZoom = ((left + i) % div) * tileSize / div;
int yZoom = ((top + j) % div) * tileSize / div;
bitmapToZoom.set(xZoom, yZoom, xZoom + tileSize / div, yZoom + tileSize / div);
bitmapToDraw.set(x1, y1, x1 + ftileSize, y1 + ftileSize);
canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap);
}
} else {
bitmapToZoom.set(0, 0, map.getTileSize(), map.getTileSize());
bitmapToDraw.set(x1, y1, x1 + ftileSize, y1 + ftileSize);
canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap);
}
}
}
} else {
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
float x1 = (i + left - tileX) * ftileSize + w;
float y1 = (j + top - tileY) * ftileSize + h;
drawEmptyTile(canvas, x1, y1, ftileSize, nightMode);
}
}
}
drawOverMap(canvas, latlonRect, nightMode);
} finally {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
public boolean mapIsRefreshing() {
return handler.hasMessages(1);
}
public boolean mapIsAnimating() {
return animatedDraggingThread != null && animatedDraggingThread.isAnimating();
}
// this method could be called in non UI thread
public void refreshMap() {
if (!handler.hasMessages(1)) {
Message msg = Message.obtain(handler, new Runnable() {
@Override
public void run() {
refreshMapInternal();
}
});
msg.what = 1;
handler.sendMessageDelayed(msg, 20);
}
}
public void tileDownloaded(DownloadRequest request) {
if (request == null || rotate != 0) {
// if image is rotated call refresh the whole canvas
// because we can't find dirty rectangular region
// if request null then we don't know exact images were changed
refreshMap();
return;
}
if (request.error) {
return;
}
if (request.zoom != getZoom()) {
refreshMap();
return;
}
float w = getCenterPointX();
float h = getCenterPointY();
float tileX = getXTile();
float tileY = getYTile();
SurfaceHolder holder = getHolder();
synchronized (holder) {
tilesRect.set(request.xTile, request.yTile, request.xTile + 1, request.yTile + 1);
calculatePixelRectangle(boundsRect, w, h, tileX, tileY, tilesRect);
if (boundsRect.left > getWidth() || boundsRect.right < 0 || boundsRect.bottom < 0 || boundsRect.top > getHeight()) {
return;
}
Canvas canvas = holder.lockCanvas(boundsRect);
if (canvas != null) {
boolean nightMode = false;
if(application != null){
Boolean dayNightRenderer = application.getDaynightHelper().getDayNightRenderer();
if(dayNightRenderer != null){
nightMode = !dayNightRenderer.booleanValue();
}
}
canvas.save();
canvas.rotate(rotate, w, h);
try {
Bitmap bmp = null;
if (map != null) {
ResourceManager mgr = getApplication().getResourceManager();
bmp = mgr.getTileImageForMapSync(null, map, request.xTile, request.yTile, request.zoom, false);
}
float x = (request.xTile - tileX) * getTileSize() + w;
float y = (request.yTile - tileY) * getTileSize() + h;
float tileSize = getTileSize();
if (bmp == null) {
drawEmptyTile(canvas, x, y, tileSize, nightMode);
} else {
bitmapToZoom.set(0, 0, getSourceTileSize(), getSourceTileSize());
bitmapToDraw.set(x, y, x + tileSize, y + tileSize);
canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap);
}
drawOverMap(canvas, latlonRect, nightMode);
} finally {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
// ///////////////////////////////// DRAGGING PART ///////////////////////////////////////
public float calcDiffTileY(float dx, float dy) {
return (-rotateSin * dx + rotateCos * dy) / getTileSize();
}
public float calcDiffTileX(float dx, float dy) {
return (rotateCos * dx + rotateSin * dy) / getTileSize();
}
public float calcDiffPixelY(float dTileX, float dTileY) {
return (rotateSin * dTileX + rotateCos * dTileY) * getTileSize();
}
public float calcDiffPixelX(float dTileX, float dTileY) {
return (rotateCos * dTileX - rotateSin * dTileY) * getTileSize();
}
/**
* These methods do not consider rotating
*/
public int getMapXForPoint(double longitude) {
double tileX = MapUtils.getTileNumberX(getZoom(), longitude);
return (int) ((tileX - getXTile()) * getTileSize() + getCenterPointX());
}
public int getMapYForPoint(double latitude) {
double tileY = MapUtils.getTileNumberY(getZoom(), latitude);
return (int) ((tileY - getYTile()) * getTileSize() + getCenterPointY());
}
public int getRotatedMapXForPoint(double latitude, double longitude) {
int cx = getCenterPointX();
double xTile = MapUtils.getTileNumberX(getZoom(), longitude);
double yTile = MapUtils.getTileNumberY(getZoom(), latitude);
return (int) (calcDiffPixelX((float) (xTile - getXTile()), (float) (yTile - getYTile())) + cx);
}
public int getRotatedMapYForPoint(double latitude, double longitude) {
int cy = getCenterPointY();
double xTile = MapUtils.getTileNumberX(getZoom(), longitude);
double yTile = MapUtils.getTileNumberY(getZoom(), latitude);
return (int) (calcDiffPixelY((float) (xTile - getXTile()), (float) (yTile - getYTile())) + cy);
}
public boolean isPointOnTheRotatedMap(double latitude, double longitude) {
int cx = getCenterPointX();
int cy = getCenterPointY();
double xTile = MapUtils.getTileNumberX(getZoom(), longitude);
double yTile = MapUtils.getTileNumberY(getZoom(), latitude);
int newX = (int) (calcDiffPixelX((float) (xTile - getXTile()), (float) (yTile - getYTile())) + cx);
int newY = (int) (calcDiffPixelY((float) (xTile - getXTile()), (float) (yTile - getYTile())) + cy);
if (newX >= 0 && newX <= getWidth() && newY >= 0 && newY <= getHeight()) {
return true;
}
return false;
}
@Override
public void dragTo(float fromX, float fromY, float toX, float toY, boolean notify) {
float dx = (fromX - toX);
float dy = (fromY - toY);
moveTo(dx, dy);
if (locationListener != null && notify) {
locationListener.locationChanged(latitude, longitude, this);
}
}
@Override
public void rotateTo(float rotate) {
this.rotate = rotate;
float rotateRad = (float) Math.toRadians(rotate);
this.rotateCos = FloatMath.cos(rotateRad);
this.rotateSin = FloatMath.sin(rotateRad);
refreshMap();
}
@Override
public void setLatLon(double latitude, double longitude, boolean notify) {
this.latitude = latitude;
this.longitude = longitude;
refreshMap();
if (locationListener != null && notify) {
locationListener.locationChanged(latitude, longitude, this);
}
}
public void moveTo(float dx, float dy) {
float fy = calcDiffTileY(dx, dy);
float fx = calcDiffTileX(dx, dy);
this.latitude = MapUtils.getLatitudeFromTile(getZoom(), getYTile() + fy);
this.longitude = MapUtils.getLongitudeFromTile(getZoom(), getXTile() + fx);
refreshMap();
// do not notify here listener
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
animatedDraggingThread.stopAnimating();
}
if (!multiTouchSupport.onTouchEvent(event)) {
/* return */gestureDetector.onTouchEvent(event);
}
return true;
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (trackBallDelegate != null) {
trackBallDelegate.onTrackBallEvent(event);
}
return super.onTrackballEvent(event);
}
public void setTrackBallDelegate(OnTrackBallListener trackBallDelegate) {
this.trackBallDelegate = trackBallDelegate;
}
public void setOnLongClickListener(OnLongClickListener l) {
this.onLongClickListener = l;
}
public void setOnClickListener(OnClickListener l) {
this.onClickListener = l;
}
@Override
public boolean onDown(MotionEvent e) {
// enable double tap animation
// animatedDraggingThread.stopAnimating();
return false;
}
@Override
public void onZoomEnded(float distance, float relativeToStart) {
float dz = (float) (Math.log(relativeToStart) / Math.log(2) * 1.5);
float calcZoom = initialMultiTouchZoom + dz;
setZoom(Math.round(calcZoom));
zoomPositionChanged(getZoom());
}
@Override
public void onZoomStarted(float distance, PointF centerPoint) {
initialMultiTouchCenterPoint = centerPoint;
initialMultiTouchLocation = getLatLonFromScreenPoint(centerPoint.x, centerPoint.y);
initialMultiTouchZoom = zoom;
}
private void zoomPositionChanged(float calcZoom) {
float dx = initialMultiTouchCenterPoint.x - getCenterPointX();
float dy = initialMultiTouchCenterPoint.y - getCenterPointY();
float ex = calcDiffTileX(dx, dy);
float ey = calcDiffTileY(dx, dy);
int z = (int)calcZoom;
double tx = MapUtils.getTileNumberX(z, initialMultiTouchLocation.getLongitude());
double ty = MapUtils.getTileNumberY(z, initialMultiTouchLocation.getLatitude());
double lat = MapUtils.getLatitudeFromTile(z, ty - ey);
double lon = MapUtils.getLongitudeFromTile(z, tx - ex);
setLatLon(lat, lon);
}
@Override
public void onZooming(float distance, float relativeToStart) {
float dz = (float) (Math.log(relativeToStart) / Math.log(2) * 1.5);
float calcZoom = initialMultiTouchZoom + dz;
if (Math.abs(calcZoom - zoom) > 0.05) {
setZoom(calcZoom);
zoomPositionChanged(calcZoom);
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(e1.getX() - e2.getX()) + Math.abs(e1.getX() - e2.getX()) > 50 * dm.density) {
animatedDraggingThread.startDragging(Math.abs(velocityX / 1000), Math.abs(velocityY / 1000), e1.getX(), e1.getY(), e2.getX(),
e2.getY());
} else {
onScroll(e1, e2, e1.getX() - e2.getX(), e1.getY() - e2.getY());
}
return true;
}
public AnimateDraggingMapThread getAnimatedDraggingThread() {
return animatedDraggingThread;
}
@Override
public void onLongPress(MotionEvent e) {
if (multiTouchSupport.isInZoomMode()) {
return;
}
if (log.isDebugEnabled()) {
log.debug("On long click event " + e.getX() + " " + e.getY()); //$NON-NLS-1$ //$NON-NLS-2$
}
PointF point = new PointF(e.getX(), e.getY());
for (int i = layers.size() - 1; i >= 0; i--) {
if (layers.get(i).onLongPressEvent(point)) {
return;
}
}
if (onLongClickListener != null && onLongClickListener.onLongPressEvent(point)) {
return;
}
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
dragTo(e2.getX() + distanceX, e2.getY() + distanceY, e2.getX(), e2.getY(), true);
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
PointF point = new PointF(e.getX(), e.getY());
if (log.isDebugEnabled()) {
log.debug("On click event " + point.x + " " + point.y); //$NON-NLS-1$ //$NON-NLS-2$
}
for (int i = layers.size() - 1; i >= 0; i--) {
if (layers.get(i).onTouchEvent(point)) {
return true;
}
}
if (onClickListener != null && onClickListener.onPressEvent(point)) {
return true;
}
return false;
}
public LatLon getLatLonFromScreenPoint(float x, float y) {
float dx = x - getCenterPointX();
float dy = y - getCenterPointY();
float fy = calcDiffTileY(dx, dy);
float fx = calcDiffTileX(dx, dy);
double latitude = MapUtils.getLatitudeFromTile(getZoom(), getYTile() + fy);
double longitude = MapUtils.getLongitudeFromTile(getZoom(), getXTile() + fx);
return new LatLon(latitude, longitude);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
LatLon l = getLatLonFromScreenPoint(e.getX(), e.getY());
getAnimatedDraggingThread().startMoving(getLatitude(), getLongitude(), l.getLatitude(), l.getLongitude(), getZoom(), getZoom() + 1,
getSourceTileSize(), getRotate(), true);
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
}