/*************************************************************************
* Copyright (c) 2015 Lemberg Solutions
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**************************************************************************/
package com.ls.widgets.map;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.xml.sax.SAXException;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.Scroller;
import android.widget.ZoomButtonsController;
import android.widget.ZoomButtonsController.OnZoomListener;
import com.ls.widgets.map.config.GPSConfig;
import com.ls.widgets.map.config.MapConfigParser;
import com.ls.widgets.map.config.MapGraphicsConfig;
import com.ls.widgets.map.config.OfflineMapConfig;
import com.ls.widgets.map.events.MapScrolledEvent;
import com.ls.widgets.map.events.MapTouchedEvent;
import com.ls.widgets.map.events.ObjectTouchEvent;
import com.ls.widgets.map.interfaces.Layer;
import com.ls.widgets.map.interfaces.MapEventsListener;
import com.ls.widgets.map.interfaces.MapLocationListener;
import com.ls.widgets.map.interfaces.OnGridReadyListener;
import com.ls.widgets.map.interfaces.OnLocationChangedListener;
import com.ls.widgets.map.interfaces.OnMapDoubleTapListener;
import com.ls.widgets.map.interfaces.OnMapLongClickListener;
import com.ls.widgets.map.interfaces.OnMapScrollListener;
import com.ls.widgets.map.interfaces.OnMapTilesFinishedLoadingListener;
import com.ls.widgets.map.interfaces.OnMapTouchListener;
import com.ls.widgets.map.location.PositionMarker;
import com.ls.widgets.map.model.Grid;
import com.ls.widgets.map.model.MapLayer;
import com.ls.widgets.map.providers.AssetTileProvider;
import com.ls.widgets.map.providers.ExternalStorageTileProvider;
import com.ls.widgets.map.providers.GPSLocationProvider;
import com.ls.widgets.map.providers.TileProvider;
import com.ls.widgets.map.utils.Graphics;
import com.ls.widgets.map.utils.MathUtils;
import com.ls.widgets.map.utils.OfflineMapUtil;
import com.ls.widgets.map.utils.PivotFactory;
import com.ls.widgets.map.utils.PivotFactory.PivotPosition;
import com.ls.widgets.map.utils.Resources;
import com.ls.widgets.map.utils.TransformUtils;
public class MapWidget extends View implements MapLocationListener {
private static final String MSG_MAP_DATA_IS_CORRUPTED_OR_MISSING = "Map data is corrupted or missing.";
private final static String TAG = "MAP WIDGET";
private final static long POS_PIN_ID = 1;
private enum Mode {
NONE, ZOOMED, ZOOM
};
private OfflineMapConfig config;
private ZoomButtonsController zoomBtnsController;
private Grid grid;
private Grid prevGrid;
private Paint paint;
private float scale;
private double pinchZoomScale;
private boolean doNotZoom;
private boolean isAnimationEnabled;
private boolean byUser;
// Represents layers in the map
private MapLayer topmostLayer;
private ArrayList<MapLayer> layers;
private Map<Long, Layer> layersMap;
// Provider that handles loading of map tiles.
protected TileProvider tileProvider;
protected GPSLocationProvider locationProvider;
// Listeners
private OnMapTouchListener mapTouchListener;
private OnMapTilesFinishedLoadingListener mapTilesReadyListener;
private OnMapScrollListener mapScrollListener;
private ArrayList<MapEventsListener> mapEventsListeners;
private OnLocationChangedListener locationChangeListener;
private OnLongClickListener longClickListener;
private OnMapLongClickListener mapLongClicklistener;
private OnMapDoubleTapListener onDoubleTapListener;
private OnTouchListener onTouchListener;
private Mode mode;
// Smooth scrolling
private GestureDetector gestureDetector;
private Scroller scroller;
// debug
private boolean debugEnabled = false;
private RectF lastTouchedRect;
private boolean isZooming;
private boolean isDestroying;
private boolean userTouching;
private double pinchStartDistance;
private int mapPivotX;
private int mapPivotY;
private static Bitmap logo;
private Rect drawingRect;
private Runnable restoreScrollPosRunnable;
private Runnable performAfterZoom;
private Runnable performAfterTranslate;
private boolean requestCenterMap;
/**
* Creates instance of map widget.
*
* @param context
* - context
* @param rootMapFolder
* - folder that contains map resources inside your assets.
* @param initialZoomLevel
* - initial zoom level.
*/
public MapWidget(Context context, String rootMapFolder, int initialZoomLevel) {
this(null, context, rootMapFolder, initialZoomLevel);
}
/**
* Creates instance of map widget.
*
* @param context
* - context
* @param rootMapFolder
* - instance of File that points to the map resources which are
* located on the external storage.
* @param initialZoomLevel
* - initial zoom level
*/
public MapWidget(Context context, File rootMapFolder, int initialZoomLevel) {
this(null, context, rootMapFolder, initialZoomLevel);
}
/**
* Creates instance of map widget. Zoom level will be set to 10.
*
* @param context
* - Context
* @param rootMapFolder
* - folder that contains map resources inside your assets.
*/
public MapWidget(Context context, String rootMapFolder) {
this(null, context, rootMapFolder, 10);
}
/**
* Creates instance of map widget. Zoom level will be set to 10.
*
* @param context
* - Context
* @param rootMapFolder
* - instance of File that points to the map resources which are
* located on the external storage.
*/
public MapWidget(Context context, File rootMapFolder) {
this(null, context, rootMapFolder, 10);
}
/**
* Creates instance of map widget.
*
* @param bundle
* - bundle that were used to save map widget's state.
* @param context
* - Context
* @param rootMapFolder
* - instance of File that points to the map resources which are
* located on the external storage.
* @param initialZoomLevel
* - zoom level that will be set in case if bundle doesn't
* contain previously saved state.
*/
public MapWidget(Bundle bundle, Context context, File rootMapFolder,
int initialZoomLevel) {
super(context);
initCommonStuff(context);
String configPath = OfflineMapUtil.getConfigFilePath(rootMapFolder
.getAbsolutePath());
try {
MapConfigParser configParser = new MapConfigParser(
rootMapFolder.getAbsolutePath());
config = configParser.parse(context, new File(configPath));
if (config != null) {
tileProvider = new ExternalStorageTileProvider(config);
int maxZoomLevel = OfflineMapUtil.getMaxZoomLevel(
config.getImageWidth(), config.getImageHeight());
int zoomLevel = initialZoomLevel;
float scale = 1.0f;
if (bundle != null) {
if (bundle.containsKey("com.ls.zoomLevel"))
zoomLevel = bundle.getInt("com.ls.zoomLevel");
if (bundle.containsKey("com.ls.scale"))
scale = bundle.getFloat("com.ls.scale");
}
if (zoomLevel > maxZoomLevel) {
grid = new Grid(this, config, tileProvider, maxZoomLevel);
if (scale == 1.0f) {
scale = (float) Math.pow(2, zoomLevel - maxZoomLevel);
}
} else {
grid = new Grid(this, config, tileProvider, zoomLevel);
}
this.scale = scale;
grid.setInternalScale(scale);
initPositionPin();
restoreMapPosition(bundle);
}
} catch (SAXException e) {
Log.e(TAG, "Exception: " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(TAG, "Exception: " + e);
e.printStackTrace();
}
}
/**
* Creates instance of map widget.
*
* @param bundle
* - bundle that were used to save map widget's state.
* @param context
* - Context
* @param rootMapFolder
* - folder that contains map resources inside your assets.
* @param initialZoomLevel
* - zoom level that will be set in case if bundle doesn't
* contain previously saved state.
*/
public MapWidget(Bundle bundle, Context context, String rootMapFolder,
int initialZoomLevel) {
super(context);
initCommonStuff(context);
String configPath = OfflineMapUtil.getConfigFilePath(rootMapFolder);
try {
MapConfigParser configParser = new MapConfigParser(rootMapFolder);
config = configParser.parse(context, configPath);
tileProvider = new AssetTileProvider(getContext(), config);
int maxZoomLevel = OfflineMapUtil.getMaxZoomLevel(
config.getImageWidth(), config.getImageHeight());
int zoomLevel = initialZoomLevel;
float scale = 1.0f;
if (bundle != null) {
if (bundle.containsKey("com.ls.zoomLevel"))
zoomLevel = bundle.getInt("com.ls.zoomLevel");
if (bundle.containsKey("com.ls.scale"))
scale = bundle.getFloat("com.ls.scale");
}
if (zoomLevel > maxZoomLevel) {
grid = new Grid(this, config, tileProvider, maxZoomLevel);
if (scale == 1.0f) {
scale = (float) Math.pow(2, zoomLevel - maxZoomLevel);
}
} else {
grid = new Grid(this, config, tileProvider, zoomLevel);
}
this.scale = scale;
grid.setInternalScale(scale);
initPositionPin();
restoreMapPosition(bundle);
} catch (SAXException e) {
Log.e(TAG, "Exception: " + e);
e.printStackTrace();
} catch (IOException e) {
Log.e(TAG, "Exception: " + e);
e.printStackTrace();
}
}
private void restoreMapPosition(Bundle bundle) {
if (bundle != null && bundle.containsKey("com.ls.curPosOnMapX")) {
final int mapX = (int) bundle.getFloat("com.ls.curPosOnMapX");
final int mapY = (int) bundle.getFloat("com.ls.curPosOnMapY");
Log.d("MapWidget", "Restored pos: [" + mapX + "," + mapY + "]");
restoreScrollPosRunnable = new Runnable() {
public void run() {
jumpTo(new Point(mapX, mapY));
};
};
} else {
doCorrectPosition(false, false);
}
}
private void initCommonStuff(Context context) {
scale = 1.0f;
mode = Mode.NONE;
drawingRect = new Rect();
isAnimationEnabled = true;
requestCenterMap = false;
userTouching = false;
this.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
setBackgroundDrawable(null);
this.setClickable(true);
this.setEnabled(true);
this.setFocusable(true);
initializeZoomBtnsController();
gestureDetector = new GestureDetector(context, new MyGestureDetector());
decelerateInterpolator = new DecelerateInterpolator(1.5f);
scroller = new Scroller(context, decelerateInterpolator);
topmostLayer = new MapLayer(1, this);
topmostLayer.setVisible(false);
layers = new ArrayList<MapLayer>();
layersMap = new HashMap<Long, Layer>();
mapEventsListeners = new ArrayList<MapEventsListener>();
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(1);
if (Resources.LOGO != null) {
logo = BitmapFactory.decodeByteArray(Resources.LOGO, 0,
Resources.LOGO.length);
}
locationProvider = null;
performAfterTranslate = null;
}
private void initPositionPin() {
BitmapDrawable arrow = new BitmapDrawable(getResources(),
BitmapFactory.decodeByteArray(Graphics.BLUE_ARROW, 0,
Graphics.BLUE_ARROW.length));
BitmapDrawable dot = new BitmapDrawable(getResources(),
BitmapFactory.decodeByteArray(
Graphics.BLUE_DOT, 0, Graphics.BLUE_DOT.length));
PositionMarker pin = new PositionMarker(this, POS_PIN_ID, dot, arrow);
topmostLayer.addMapObject(pin);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w == 0 && h == 0)
return;
if (restoreScrollPosRunnable != null) {
restoreScrollPosRunnable.run();
restoreScrollPosRunnable = null;
}
}
protected void setTileProvider(TileProvider tileManager) {
if (grid != null) {
grid.setTileProvider(tileManager);
}
}
/**
* Adds listener for map events.
*
* @param listener
* - instance of MapEventListener
*/
public void addMapEventsListener(MapEventsListener listener) {
if (mapEventsListeners == null) {
mapEventsListeners = new ArrayList<MapEventsListener>();
}
mapEventsListeners.add(listener);
}
/**
* Creates new map layer with a given id.
*
* @param theLayerId
* - id of the new layer
* @return returns instance of the MapLayer or null if error occured.
* @throws IllegalArgumentException
* when layer with the given id exists already.
*/
public MapLayer createLayer(long theLayerId) {
if (this.layersMap.containsKey(theLayerId)) {
throw new IllegalArgumentException(
"Attempt to create layer with duplicated ID");
}
try {
MapLayer layer = new MapLayer(theLayerId, this);
layers.add(layer);
layersMap.put(theLayerId, layer);
return layer;
} catch (Exception e) {
Log.e("MapWidget", "Exception: " + e);
return null;
}
}
/**
* Removes layer with the given id from the map.
*
* @param theLayerId
* the id of previously created layer.
*/
public void removeLayer(long theLayerId) {
Layer layer = layersMap.remove(theLayerId);
layers.remove(layer);
}
/**
* Removes all layers from the map.
*/
public void removeAllLayers() {
layers.clear();
layersMap.clear();
}
/**
* Centers the map horizontally
*/
public void centerMapHorizontally() {
if (grid.getWidth() > getWidth()) {
int dx = (getWidth() - grid.getWidth()) / 2;
scrollBy(-dx, 0);
}
}
/**
* Returns map layer by index.
*
* @param index
* the index of the layer
* @return instance of Layer
* @throws ArrayIndexOutOfBoundsException
* when index is out of bounds.
*/
public Layer getLayer(int index) {
return layers.get(index);
}
/**
* Returns map layer by layer id.
*
* @param id
* - layer id
* @return instance of Layer
*/
public Layer getLayerById(long id) {
return layersMap.get(id);
}
/**
* Returns total layer count
*
* @return layer count
*/
public int getLayerCount() {
return layers.size();
}
/**
* Returns height of the map taking current scale into account.
*
* @return height of the map in pixels.
*/
public int getMapHeight() {
if (grid != null) {
return grid.getHeight();
}
return 0;
}
/**
* Returns width of the map taking current scale into account.
*
* @return width of the map in pixels.
*/
public int getMapWidth() {
if (grid != null) {
return grid.getWidth();
}
return 0;
}
/**
* Returns the height of the map on the max zoom level.
*
* @return original map height in pixels.
*/
public int getOriginalMapHeight() {
if (grid != null) {
return grid.getOriginalHeight();
}
return 0;
}
/**
* Returns the width of the map on the max zoom level.
*
* @return original map width in pixels.
*/
public int getOriginalMapWidth() {
if (grid != null) {
return grid.getOriginalWidth();
}
return 0;
}
/**
* Returns the copy of the map widget's configuration
*
* @return instance of OfflineMapConfig class.
*/
public OfflineMapConfig getConfig() {
return config;
}
/**
* Returns the current scale of the map.
*
* @return float that represents the scale of the map. 1.0 = max zoom level.
* 0.5 - map is scaled down to half of it's size.
*/
public float getScale() {
if (grid != null) {
return (float) grid.getScale();
}
return 0;
}
/**
* Returns current zoom level of the map.
*
* @return current zoom level of the map. Should be greater than 0.
*/
public int getZoomLevel() {
if (grid == null) {
return 0;
}
double scale = grid.getScale();
int zoomLevel = grid.getZoomLevel();
if (scale <= 1.0f) {
return zoomLevel;
} else {
return OfflineMapUtil.getMaxZoomLevel(grid.getWidth(),
grid.getHeight());
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (config == null) {
return false;
}
int keyCode2 = event.getKeyCode();
switch (keyCode2) {
case KeyEvent.KEYCODE_DPAD_LEFT:
scrollBy(-config.getTrackballScrollStepX(), 0);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
scrollBy(config.getTrackballScrollStepX(), 0);
return true;
case KeyEvent.KEYCODE_DPAD_UP:
scrollBy(0, -config.getTrackballScrollStepY());
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
scrollBy(0, config.getTrackballScrollStepY());
return true;
case KeyEvent.KEYCODE_I:
zoomIn();
return true;
case KeyEvent.KEYCODE_O:
zoomOut();
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (config == null) {
return false;
}
if (isDestroying) {
Log.w("MapWidget", "Map is destroying... OnTouch skipped");
return false;
}
if (this.onTouchListener != null) {
// this.onTouchListener.onTouch(this, event);
}
boolean result = false;
super.onTouchEvent(event);
gestureDetector.onTouchEvent(event);
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
if (actionCode == MotionEvent.ACTION_DOWN) {
byUser = true;
userTouching = true;
try {
if (config.isZoomBtnsVisible()) {
zoomBtnsController.setVisible(true);
}
} catch (Exception e) {
Log.w("MapWidget", "Exception e: " + e);
}
tileProvider.pauseProcessingCommands();
result = true;
} else if (actionCode == MotionEvent.ACTION_POINTER_DOWN) {
if (config.isPinchZoomEnabled()) {
mode = Mode.ZOOM;
doNotZoom = false;
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
pinchStartDistance = MathUtils.distance(x1, y1, x2, y2);
PointF mapPivot = MathUtils.middle(x1, y1, x2, y2);
mapPivotX = (int) (mapPivot.x);
mapPivotY = (int) (mapPivot.y);
result = true;
}
} else if (actionCode == MotionEvent.ACTION_MOVE) {
if (zoomBtnsController != null && config.isZoomBtnsVisible()
&& !zoomBtnsController.isVisible())
zoomBtnsController.setVisible(true);
if (mode == Mode.ZOOM) {
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
double pinchDistance = MathUtils.distance(x1, y1, x2, y2);
if (pinchStartDistance != 0) {
pinchZoomScale = pinchDistance / pinchStartDistance;
if (pinchZoomScale >= 1.025) {
mode = Mode.ZOOMED;
zoomIn(mapPivotX, mapPivotY);
} else if (pinchZoomScale <= 0.975) {
mode = Mode.ZOOMED;
zoomOut();
}
}
}
result = true;
} else if (actionCode == MotionEvent.ACTION_POINTER_UP) {
mode = Mode.NONE;
result = true;
} else if (actionCode == MotionEvent.ACTION_UP) {
byUser = false;
userTouching = false;
if (!isZooming) {
doCorrectPosition();
}
result = true;
}
return result;
}
/**
* Removes map event listener from the map.
*
* @param listener
* instance of the MapEventsListener
*/
public void removeMapEventsListener(MapEventsListener listener) {
if (mapEventsListeners != null) {
mapEventsListeners.remove(listener);
}
}
/**
* Removes all map event listeners.
*/
public void removeAllMapEventsListeners() {
if (mapEventsListeners != null) {
mapEventsListeners.clear();
}
mapEventsListeners = new ArrayList<MapEventsListener>();
}
/**
* Removes all graphical object from all layers
*/
public void clearLayers() {
if (Looper.myLooper() == null) {
throw new IllegalThreadStateException(
"Should be called from UI thread");
}
for (int i = layers.size() - 1; i >= 0; --i) {
layers.get(i).clearAll();
}
layers.clear();
}
/**
* Sets touch listener to the map.
*
* @param mapTouchListener
* instance of OnMapTouchListener.
*/
public void setOnMapTouchListener(OnMapTouchListener mapTouchListener) {
this.mapTouchListener = mapTouchListener;
}
/**
* Sets the listener to handle long click on the map.
* @param onMapLongClickListener - instance of {@link OnMapLongClickListener}. Can be null.
*/
public void setOnMapLongClickListener(OnMapLongClickListener onMapLongClickListener)
{
this.mapLongClicklistener = onMapLongClickListener;
}
/**
* Sets the listener that will be called when all visible tiles has been
* loaded and displayed.
*
* @param listener
* instance of OnMapTilesFinishedLoadingListener or null.
*/
public void setOnMapTilesFinishLoadingListener(
OnMapTilesFinishedLoadingListener listener) {
this.mapTilesReadyListener = listener;
if (grid != null) {
grid.setOnReadyListener(new OnGridReadyListener() {
@Override
public void onReady() {
if (mapTilesReadyListener != null) {
mapTilesReadyListener.onMapTilesFinishedLoading();
}
}
});
}
}
/**
* Sets scroll listener to the map.
*
* @param mapScrollListener
* instance of OnMapScrollListener.
*/
public void setOnMapScrolledListener(OnMapScrollListener mapScrollListener) {
this.mapScrollListener = mapScrollListener;
}
/**
* Sets listener for retrieving the location
*
* @param listener
* - instance of OnLocationChangedListener. May be null.
*/
public void setOnLocationChangedListener(OnLocationChangedListener listener) {
this.locationChangeListener = listener;
}
/**
* Sets the listener to handle long click event.
*
* @param listener
* instance of OnLongClickListener. Can be null.
* @see android.view.View.OnLongClickListener
*/
@Override
public void setOnLongClickListener(OnLongClickListener listener) {
this.longClickListener = listener;
}
/**
* Sets the listener to handle double tap event.
* @param listener instance of OnMapDoubleTapListener. Can be null.
* @see com.ls.widgets.map.interfaces.OnMapDoubleTapListener
*/
public void setOnDoubleTapListener(OnMapDoubleTapListener listener)
{
this.onDoubleTapListener = listener;
}
/**
* Sets the listener to handle touch events
* @param listener instance of OnTouchListener
* @see android.view.View.OnTouchListener
*/
@Override
public void setOnTouchListener(OnTouchListener listener)
{
super.setOnTouchListener(listener);
}
/**
* Enables or disables the standard zoom controls.
*
* @param enabled
* true in order to make zoom controls visible, otherwise false.
*/
public void setZoomButtonsVisible(boolean enabled) {
if (config != null) {
config.setZoomBtnsVisible(enabled);
if (enabled) {
if (zoomBtnsController == null) {
initializeZoomBtnsController();
}
} else {
if (zoomBtnsController != null) {
zoomBtnsController.setVisible(false);
zoomBtnsController.setOnZoomListener(null);
zoomBtnsController = null;
}
}
} else {
Log.w(TAG, "Ignored. Map is not initialized properly.");
}
}
/**
* Sets the min zoom level the user can zoom out to.
*
* @param minZoomLevel
* int from 0 to count of zoom levels.
*/
public void setMinZoomLevel(int minZoomLevel) {
if (config == null) {
Log.w(TAG,
"setMinZoomLevel skipped. MapWidget is not initialized properly");
return;
}
int maxAvailableZoomLevel = grid.getMaxZoomLevel();
int minAvailableZoomLevel = grid.getMinZoomLevel();
if (minZoomLevel < minAvailableZoomLevel) {
Log.w(TAG, "There is no " + minZoomLevel + " zoom level. Will use "
+ minAvailableZoomLevel + " as min zoom level.");
config.setMinZoomLevelLimit(minZoomLevel);
} else if (minZoomLevel > maxAvailableZoomLevel) {
Log.w(TAG,
"Min zoom level should be less than max zoom level. Min zoom level: "
+ minAvailableZoomLevel + " Max zoom level: "
+ maxAvailableZoomLevel + ", "
+ " You are setting: "
+ config.getMaxZoomLevelLimit()
+ " as min zoom level.");
Log.w(TAG, "Will use max zoom level as min zoom level.");
config.setMinZoomLevelLimit(maxAvailableZoomLevel);
} else {
config.setMinZoomLevelLimit(minZoomLevel);
}
updateZoomButtons();
}
/**
* The max zoom level the user can zoom in to.
*
* @param maxZoomLevel
* int from 0 to max zoom levels.
*/
public void setMaxZoomLevel(int maxZoomLevel) {
if (config == null) {
Log.w(TAG,
"setMaxZoomLevel skipped. MapWidget was not initialized properly");
return;
}
if (grid == null) {
throw new IllegalStateException();
}
int maxAvailableZoomLevel = grid.getMaxZoomLevel();
int minAvailableZoomLevel = grid.getMinZoomLevel();
if (!config.isSoftwareZoomEnabled()
&& maxZoomLevel > maxAvailableZoomLevel) {
Log.w(TAG, "There is no " + maxZoomLevel + " zoom level. Will use "
+ maxAvailableZoomLevel + " as max zoom level.");
config.setMaxZoomLevelLimit(maxAvailableZoomLevel);
} else if (maxZoomLevel < minAvailableZoomLevel) {
Log.w(TAG,
"Max zoom level should be greater than min zoom level. Min zoom level: "
+ minAvailableZoomLevel + " Max zoom level: "
+ maxAvailableZoomLevel + ", "
+ " you are setting: " + maxZoomLevel
+ " as max zoom level.");
Log.w(TAG, "Will use min zoom level as max zoom level.");
config.setMaxZoomLevelLimit(minAvailableZoomLevel);
} else {
config.setMaxZoomLevelLimit(maxZoomLevel);
}
updateZoomButtons();
}
/**
* Set's the scale to the map. Map will be scaled by resizing the existing
* tiles. Zoom level will be preserved.
*
* @param scale
* scale value. 2.0 means that you want to make map two times
* bigger.
*/
public void setScale(float scale) {
if (Looper.myLooper() == null) {
throw new IllegalThreadStateException(
"Should be called from UI thread");
}
if (grid == null) {
return;
}
grid.setSoftScale(scale);
setScaleToOtherDrawables((float) getScale());
invalidate();
}
/**
* Enables the map to keep zooming in when no more zoom levels left.
* Software scale will be used.
*
* @param useSoftwareZoom
* true if you want to enable software zoom, false otherwise.
*/
public void setUseSoftwareZoom(boolean useSoftwareZoom) {
if (config != null) {
config.setSoftwareZoomEnabled(useSoftwareZoom);
}
}
/**
* Enables/disables the zoom in/zoom out animations
*
* @param isEnabled
* true if you want to enable the animations, false otherwise.
*/
public void setAnimationEnabled(boolean isEnabled) {
this.isAnimationEnabled = isEnabled;
}
/**
* Sets the size of the touch area when user touches the map.
*
* @param pixels
* radius of the touch area in pixels
*/
public void setTouchAreaSize(int pixels) {
if (config != null) {
config.setTouchAreaSize(pixels);
}
}
/**
* Zooms map in by one zoom level.
*/
public void zoomIn() {
final int pivotX = (getWidth() / 2);
final int pivotY = (getHeight() / 2);
zoomIn(pivotX, pivotY);
}
/**
* Shows current position of the user on the map. You can configure the view
* of the pointer. See MapWidget.getMapGraphicsConfig() for details. You can
* configure the GPS receiver by setting configuration parameters before
* setShowMyPosition is called. See MapWidget.getGPSConfig() for details.
* <p>
*
* In order to calibrate the map you should add the calibration data to the
* map.xml. Calibration data consists of two points - top left and bottom
* right. X and Y is a coordinate of the point in your original map image in
* pixels. lat and lon is latitude and longitude of the same point in real
* world.<br>
*
* <pre>
* For example, your map.xml may look like this:<br>
* {@code
* <Image TileSize="256" Overlap="1" Format="png">
* <Size Width="1918" Height="978"/>
* <CalibrationRect>
* <Point x="0" y="0" lat="42.924251753870685" lon="-103.99658203125" topLeft="1"/>
* <Point x="1918" y="978" lat="40.81380923056961" lon="-98.3056640625"/>
* </CalibrationRect>
* </Image>
* }
* </pre>
*
* @param show
* - set true in order to show the position marker on the map,
* false - in order to hide it.
* @throws java.lang.IllegalStateException
* () in case if map is not calibrated.
*/
public void setShowMyPosition(boolean show) {
GPSConfig config = getConfig().getGpsConfig();
if (!config.isMapCalibrated()) {
throw new IllegalStateException(
"Map is not calibrated in order to use gps positioning");
}
if (show) {
MapGraphicsConfig graphics = getConfig().getGraphicsConfig();
PositionMarker marker = (PositionMarker) topmostLayer
.getMapObject(POS_PIN_ID);
if (graphics.getDotPointerDrawableId() != -1) {
Drawable dot = getResources().getDrawable(
graphics.getDotPointerDrawableId());
marker.setDotPointer(dot, PivotFactory.createPivotPoint(dot,
PivotPosition.PIVOT_CENTER));
}
if (graphics.getArrowPointerDrawableId() != -1) {
Drawable arrow = getResources().getDrawable(
graphics.getArrowPointerDrawableId());
marker.setArrowPointer(arrow, PivotFactory.createPivotPoint(
arrow, PivotPosition.PIVOT_CENTER));
}
marker.setColor(graphics.getAccuracyAreaColor(),
graphics.getAccuracyAreaBorderColor());
if (locationProvider == null) {
locationProvider = new GPSLocationProvider(this.getContext());
locationProvider.setMinRefreshTime(config.getMinTime());
locationProvider.setMinRefreshDistance(config.getMinDistance());
locationProvider.setMapLocationListener(this);
}
locationProvider.start(config.getPassiveMode());
} else {
if (locationProvider != null) {
locationProvider.stop();
}
}
}
/**
* @return instance of MapGraphicsConfig or null, if map was not configured
* properly
*/
public MapGraphicsConfig getMapGraphicsConfig() {
if (config != null) {
return config.getGraphicsConfig();
}
return null;
}
/**
* Returns GPSConfig object that will allow you to configure the GPS
* receiver.
*
* @return instance of GPSConfig, or null if map configuration file doesn't
* contain GPS calibration data or file was not found.
*/
public GPSConfig getGpsConfig() {
if (config != null)
return config.getGpsConfig();
return null;
}
public void zoomIn(final int pivotX, final int pivotY) {
if (doNotZoom) {
Log.d(TAG, "Zoom is in progress. Skipped...");
return;
}
if (config == null) {
Log.w(TAG, "Zoom in skipped. Map was not initialized properly");
return;
}
if (!config.isSoftwareZoomEnabled()
&& getZoomLevel() == grid.getMaxZoomLevel()) {
return;
} else if (config.isSoftwareZoomEnabled()
&& config.getMaxZoomLevelLimit() != 0
&& getZoomLevel() >= config.getMaxZoomLevelLimit()) {
return;
}
if (Looper.myLooper() == null) {
throw new IllegalThreadStateException(
"Should be called from UI thread");
}
notifyAboutPreZoomIn(mapEventsListeners);
isZooming = true;
doNotZoom = true;
if (!isAnimationEnabled) {
doZoom(getZoomLevel() + 1, pivotX, pivotY);
isZooming = false;
doCorrectPosition();
return;
}
performAfterZoom = new Runnable() {
@Override
public void run() {
doZoom(getZoomLevel() + 1, pivotX, pivotY);
isZooming = false;
doCorrectPosition(true);
}
};
animateZoomIn(null, pivotX, pivotY);
}
/**
* Zooms map out by one zoom level.
*/
public void zoomOut() {
if (doNotZoom) {
Log.d(TAG, "Zoom is in progress. Skipped...");
return;
}
if (config == null) {
Log.w(TAG, "Zoom in skipped. Map was not initialized properly");
return;
}
int currZoomLevel = getZoomLevel();
if (currZoomLevel == 0
|| currZoomLevel <= config.getMinZoomLevelLimit()) {
return;
}
doNotZoom = true;
if (grid == null) {
Log.w(TAG, "zoomOut() grid is null");
doNotZoom = false;
return;
}
if (Looper.myLooper() == null) {
throw new IllegalThreadStateException(
"Should be called from UI thread");
}
int pivotX = getWidth() / 2;
int pivotY = getHeight() / 2;
notifyAboutPreZoomOut(mapEventsListeners);
doZoom(currZoomLevel - 1, pivotX, pivotY);
if (!isAnimationEnabled) {
doNotZoom = false;
isZooming = false;
doCorrectPosition();
return;
}
isZooming = true;
performAfterZoom = new Runnable() {
public void run() {
isZooming = false;
doCorrectPosition(true);
}
};
animateZoomOut(null);
}
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();
Animation animation = getAnimation();
if (animation == null) {
Log.w(TAG, "Unknown animation has been finished.");
}
if (animation instanceof ScaleAnimation && performAfterZoom != null) {
performAfterZoom.run();
performAfterZoom = null;
}
if (animation instanceof TranslateAnimation
&& performAfterTranslate != null) {
performAfterTranslate.run();
performAfterTranslate = null;
}
}
@Override
protected void onDraw(Canvas canvas) {
this.getDrawingRect(drawingRect);
if (config != null) {
if (prevGrid != null) {
prevGrid.draw(canvas, paint, drawingRect);
}
if (grid != null) {
grid.draw(canvas, paint, drawingRect);
}
drawLayers(canvas, drawingRect);
if (logo != null) {
canvas.drawBitmap(logo,
getWidth() + getScrollX() - logo.getWidth() - 10,
getHeight() + getScrollY() - logo.getHeight() - 10,
null);
}
} else {
scrollTo(0, 0);
drawMissingDataErrorMessage(canvas);
}
}
private void drawMissingDataErrorMessage(Canvas canvas) {
paint.setTextSize(24);
paint.setStyle(Style.FILL);
paint.setAntiAlias(true);
paint.setSubpixelText(true);
paint.setColor(Color.BLACK);
canvas.drawPaint(paint);
paint.setColor(Color.WHITE);
Rect rect = new Rect();
paint.getTextBounds(MSG_MAP_DATA_IS_CORRUPTED_OR_MISSING, 0,
MSG_MAP_DATA_IS_CORRUPTED_OR_MISSING.length(), rect);
Rect rect2 = canvas.getClipBounds();
canvas.drawText(MSG_MAP_DATA_IS_CORRUPTED_OR_MISSING,
// (getWidth() - rect.width()) / 2, getHeight() / 2, paint);
(rect2.width() - rect.width()) / 2, rect2.height() / 2, paint);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (requestCenterMap) {
requestCenterMap = false;
final ViewTreeObserver observer = this.getViewTreeObserver();
if (observer.isAlive()) {
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
doCorrectPosition(true, false);
jumpTo(getOriginalMapWidth() / 2,
getOriginalMapHeight() / 2);
observer.removeGlobalOnLayoutListener(this);
}
});
}
} else {
doCorrectPosition(false, false);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
static final MapScrolledEvent scrolledEvent = new MapScrolledEvent(0, 0);
private DecelerateInterpolator decelerateInterpolator;
@Override
protected void onScrollChanged(int horOrigin, int verOrigin, int oldl,
int oldt) {
super.onScrollChanged(horOrigin, verOrigin, oldl, oldt);
if (mapScrollListener != null) {
scrolledEvent.setData(oldl - horOrigin, oldt - verOrigin, byUser);
mapScrollListener.onScrolledEvent(this, scrolledEvent);
}
}
protected void animateZoomIn(AnimationListener listener, float pivotX,
float pivotY) {
Animation zoomInAnimation = getZoomInAnimation(pivotX, pivotY);
if (listener != null) {
zoomInAnimation.setAnimationListener(listener);
}
this.startAnimation(zoomInAnimation);
}
protected void animateZoomOut(AnimationListener listener) {
Animation zoomOutAnimation = null;
if (zoomOutAnimation == null) {
zoomOutAnimation = getZoomOutAnimation();
}
if (listener != null) {
zoomOutAnimation.setAnimationListener(listener);
}
this.startAnimation(zoomOutAnimation);
}
private void doCorrectPosition() {
doCorrectPosition(false);
}
private void doCorrectPosition(boolean force) {
doCorrectPosition(force, isAnimationEnabled);
}
private void doCorrectPosition(boolean force, boolean animate) {
if (Looper.myLooper() == null)
throw new IllegalThreadStateException(
"Should be called from UI thread");
if (grid == null) {
return;
}
if (isZooming)
return;
if (!config.isMapCenteringEnabled() && !force) {
onPositionCorrected();
return;
}
int viewWidth = getWidth();
int viewHeight = getHeight();
float fromX = 0.0f;
float toX = 0.0f;
float fromY = 0.0f;
float toY = 0.0f;
boolean positionCorrected = false;
int gridWidth = getMapWidth();
int gridHeight = getMapHeight();
if (gridWidth > viewWidth) {
int dx2 = gridWidth - getScrollX();
if (getScrollX() < 0) {
fromX = getScrollX() * -1;
scrollTo(0, getScrollY());
positionCorrected = true;
} else if (dx2 < viewWidth) {
int gap = viewWidth - dx2;
fromX = -gap;
scrollTo((int) getScrollX() - gap, getScrollY());
positionCorrected = true;
}
} else {
toX = (viewWidth - gridWidth) / 2;
fromX = ((-getScrollX()) - toX);
scrollTo(-(int) toX, getScrollY());
positionCorrected = true;
toX = 0;
}
if (gridHeight > viewHeight) {
int dy2 = gridHeight - getScrollY();
if (getScrollY() < 0) {
fromY = -getScrollY();
scrollTo(getScrollX(), 0);
positionCorrected = true;
} else if (dy2 < viewHeight) {
int gap = viewHeight - dy2;
fromY = -gap;
scrollTo((int) getScrollX(), (int) (getScrollY() - gap));
positionCorrected = true;
}
} else {
toY = (viewHeight - gridHeight) / 2.0f;
fromY = ((-getScrollY()) - toY);
scrollTo(getScrollX(), -(int) toY);
positionCorrected = true;
toY = 0;
}
if (positionCorrected || force) {
if (animate) {
TranslateAnimation moveAnimation = new TranslateAnimation(
fromX, toX, fromY, toY);
performAfterTranslate = new Runnable() {
public void run() {
onPositionCorrected();
};
};
moveAnimation.setDuration(500);
moveAnimation.setInterpolator(decelerateInterpolator);
moveAnimation.setFillAfter(true);
this.startAnimation(moveAnimation);
} else {
onPositionCorrected();
}
} else {
onPositionCorrected();
}
}
private void onPositionCorrected() {
grid.freeResources();
if (!userTouching) {
tileProvider.startProcessingCommands();
}
}
private void doZoom(int zoomLevel, int pivotX, int pivotY) {
if (grid == null) {
doNotZoom = false;
return;
}
int maxZoomLevel = OfflineMapUtil.getMaxZoomLevel(
config.getImageWidth(), config.getImageHeight());
int currZoomLevel = getZoomLevel();
prevGrid = grid;
prevGrid.setLoadTiles(false);
final int gWidth = grid.getWidth();
final int gHeight = grid.getHeight();
float newScale = (float) Math.pow(2, zoomLevel - getZoomLevel());
// Resolving offsets that we need to move the map in order pivot point
// become in the center of the screen.
final Rect currRect = new Rect(-getScrollX(), -getScrollY(), gWidth
- getScrollX(), gHeight - getScrollY());
final Rect transformed = TransformUtils.scaleRect(currRect, newScale,
pivotX, pivotY);
boolean zoomIn = zoomLevel > currZoomLevel;
if ((zoomIn && currZoomLevel < maxZoomLevel)
|| (!zoomIn && currZoomLevel > 0) && scale == 1.0f) {
grid = new Grid(this, config, tileProvider, zoomLevel);
grid.setOnReadyListener(new OnGridReadyListener() {
@Override
public void onReady() {
grid.setOnReadyListener(null);
prevGrid = null;
if (mapTilesReadyListener != null) {
mapTilesReadyListener.onMapTilesFinishedLoading();
}
}
});
prevGrid.setInternalScale(newScale);
} else {
scale *= newScale;
if (prevGrid != null) {
prevGrid = null;
}
grid.setOnReadyListener(null);
grid.setLoadTiles(false);
grid.setInternalScale(scale);
grid.setLoadTiles(true);
}
updateZoomButtons();
float scale_temp = getScale();
setScaleToOtherDrawables(scale_temp);
scrollTo(-transformed.left, -transformed.top);
doNotZoom = false;
if (zoomIn) {
notifyAboutPostZoomIn(mapEventsListeners);
} else {
notifyAboutPostZoomOut(mapEventsListeners);
}
}
private Animation getZoomInAnimation(float pivotX, float pivotY) {
float fromX = 1.0f;
float fromY = 1.0f;
float toX = 2.0f;
float toY = 2.0f;
Animation zoomInAnimation = new ScaleAnimation(fromX, toX, fromY, toY,
pivotX, pivotY);
zoomInAnimation.setDuration(500);
zoomInAnimation.setInterpolator(decelerateInterpolator);
zoomInAnimation.setFillAfter(true);
return zoomInAnimation;
}
private Animation getZoomOutAnimation() {
float fromX = 2.0f;
float fromY = 2.0f;
float toX = 1.0f;
float toY = 1.0f;
float pivotX = getWidth() / 2.0f;
float pivotY = getHeight() / 2.0f;
Animation zoomOutAnimation = new ScaleAnimation(fromX, toX, fromY, toY,
pivotX, pivotY);
zoomOutAnimation.setDuration(500);
zoomOutAnimation.setInterpolator(decelerateInterpolator);
zoomOutAnimation.setFillAfter(true);
return zoomOutAnimation;
}
static final Rect touchRect = new Rect();
private ArrayList<ObjectTouchEvent> getTouchedElementIds(final int normX,
final int normY) {
ArrayList<ObjectTouchEvent> result = new ArrayList<ObjectTouchEvent>();
float d = 5.0f;
if (config != null) {
d = (float) config.getTouchAreaSize() / 2.0f;
}
touchRect.set((int) (normX - d), (int) (normY - d), (int) (normX + d),
(int) (normY + d));
for (int i = layers.size() - 1; i >= 0; --i) {
MapLayer layer = layers.get(i);
if (layer.isVisible()) {
ArrayList<Object> tempResult = layer.getTouched(touchRect);
for (Object id : tempResult) {
ObjectTouchEvent touchEvent = new ObjectTouchEvent(id, layer.getId());
result.add(touchEvent);
}
}
}
return result;
}
private void initializeZoomBtnsController() {
zoomBtnsController = new ZoomButtonsController(this);
zoomBtnsController.setOnZoomListener(new OnZoomListener()
{
@Override
public void onVisibilityChanged(boolean arg0) {
// Left unimplemented
}
@Override
public void onZoom(boolean zoomIn) {
if (zoomIn) {
try {
zoomIn();
} catch (Exception e) {
doNotZoom = false;
Log.e("MapWidget", "Exception while zoom in. " + e);
}
} else {
try {
zoomOut();
} catch (Exception e) {
doNotZoom = false;
Log.e("MapWidget", "Exception while zoom out. " + e);
}
}
}
});
}
private void drawLayers(Canvas canvas, Rect drawingRect) {
int size = layers.size();
for (int i = 0; i < size; ++i) {
MapLayer layer = layers.get(i);
layer.draw(canvas, drawingRect);
}
topmostLayer.draw(canvas, drawingRect);
}
private void setScaleToOtherDrawables(float scale) {
int size = layers.size();
topmostLayer.setScale(scale);
for (int i = 0; i < size; ++i) {
MapLayer layer = layers.get(i);
layer.setScale(scale);
}
}
private float translateXToMapCoordinate(float x) {
float scale = getScale();
if (scale != 0) {
return (x + (float) getScrollX()) / scale;
}
return 0;
}
private float translateYToMapCoordinate(float y) {
float scale = getScale();
if (scale != 0) {
return (y + (float) getScrollY()) / scale;
} else {
return 0;
}
}
private void updateZoomButtons() {
if (config == null || zoomBtnsController == null
|| config.isZoomBtnsVisible() == false)
return;
int currZoomLevel = getZoomLevel();
int minZoomLevel = Math.max(config.getMinZoomLevelLimit(),
grid.getMinZoomLevel());
int maxZoomLevel = grid.getMaxZoomLevel();
int maxZoomLevelLimit = config.getMaxZoomLevelLimit();
if (maxZoomLevelLimit != 0 && config.isSoftwareZoomEnabled()) {
maxZoomLevel = maxZoomLevelLimit;
} else if (maxZoomLevelLimit != 0 && !config.isSoftwareZoomEnabled()) {
maxZoomLevel = Math.min(maxZoomLevelLimit, maxZoomLevel);
}
if (currZoomLevel == maxZoomLevel) {
// At the max zoom level
// zoomBtnsController.setZoomInEnabled(false);
zoomBtnsController.setZoomOutEnabled(true);
if (!config.isSoftwareZoomEnabled() || maxZoomLevelLimit != 0) {
zoomBtnsController.setZoomInEnabled(false);
}
} else if (currZoomLevel == minZoomLevel) {
// At the min zoom level
zoomBtnsController.setZoomInEnabled(true);
zoomBtnsController.setZoomOutEnabled(scale > 1);
} else {
// In the middle
zoomBtnsController.setZoomInEnabled(true);
zoomBtnsController.setZoomOutEnabled(true);
}
}
protected void startProcessingRequests() {
isDestroying = false;
if (!userTouching) {
tileProvider.startProcessingCommands();
}
}
private static final void notifyAboutPreZoomIn(
ArrayList<MapEventsListener> listeners) {
for (MapEventsListener listener : listeners) {
if (listener != null) {
try {
listener.onPreZoomIn();
} catch (Exception e) {
Log.e(TAG, "Exception " + e + " on willZoomIn");
}
} else {
Log.w(TAG, "WillZoomIn: Map Events listener is null");
}
}
}
private static final void notifyAboutPostZoomIn(
ArrayList<MapEventsListener> listeners) {
for (MapEventsListener listener : listeners) {
if (listener != null) {
try {
listener.onPostZoomIn();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Exception " + e + " on didlZoomIn");
}
} else {
Log.w(TAG, "DidZoomIn: Map Events listener is null");
}
}
}
private static final void notifyAboutPreZoomOut(
ArrayList<MapEventsListener> listeners) {
for (MapEventsListener listener : listeners) {
if (listener != null) {
try {
listener.onPreZoomOut();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Exception " + e + " on willZoomOut");
}
} else {
Log.w(TAG, "WillZoomOut: Map Events listener is null");
}
}
}
private static final void notifyAboutPostZoomOut(
ArrayList<MapEventsListener> listeners) {
for (MapEventsListener listener : listeners) {
if (listener != null) {
try {
listener.onPostZoomOut();
} catch (Exception e) {
Log.e(TAG, "Exception " + e + " on didZoomOut");
}
} else {
Log.w(TAG, "DidZoomOut: Map Events listener is null");
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (tileProvider != null) {
tileProvider.startProcessingCommands();
} else {
Log.e(TAG, "Tile manager is not initialized");
}
}
@Override
protected void onDetachedFromWindow() {
if (zoomBtnsController != null) {
zoomBtnsController.setVisible(false);
}
if (tileProvider != null) {
tileProvider.stopProcessingCommands();
}
if (locationProvider != null) {
locationProvider.stop();
}
super.onDetachedFromWindow();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
super.computeScroll();
}
@Override
public void onMovePinTo(Location location) {
PositionMarker pin = (PositionMarker) topmostLayer
.getMapObject(POS_PIN_ID);
if (pin != null) {
pin.setAccuracy(location.getAccuracy());
pin.setBearing(location.getBearing());
pin.setBearingEnabled(location.hasBearing());
pin.moveTo(location);
}
notifyAboutLocationChanged(location);
}
private void notifyAboutLocationChanged(Location location) {
if (locationChangeListener != null) {
try {
locationChangeListener.onLocationChanged(this, location);
} catch (Exception e) {
Log.w(TAG, "Exception while executing onLocationChanged. " + e);
}
}
}
@Override
public void onChangePinVisibility(boolean visible) {
topmostLayer.setVisible(visible);
}
/**
* Scrolls the map to current location marker without animation. Location
* marker should be visible in order for this method to work.
*/
public void jumpToCurrentLocation() {
if (!topmostLayer.isVisible()) {
Log.i(TAG,
"Location marker is not visible. Jump to current location skipped");
return;
}
PositionMarker pin = (PositionMarker) topmostLayer
.getMapObject(POS_PIN_ID);
Point tempPoint = (pin.getPosition());
jumpTo(tempPoint);
}
/**
* Scrolls the map to current location marker using scroll animation.
* Location marker should be visible in order for this method to work.
*/
public void scrollToCurrentLocation() {
if (!topmostLayer.isVisible()) {
Log.i(TAG,
"Location pin is not visible. Scroll to current location skipped");
return;
}
PositionMarker pin = (PositionMarker) topmostLayer
.getMapObject(POS_PIN_ID);
Point tempPoint = (pin.getPosition());
scrollMapTo(tempPoint);
}
/**
* Scrolls the map to specific location without animation.
*
* @param location
* - instance of {@link android.location.Location} object.
* @throws IllegalStateException
* if map was not calibrated. For more details see
* MapWidget.setShowMyPosition().
*/
public void jumpTo(Location location) {
if (config == null) {
Log.w(TAG, "Jump to skipped. Map is not initialized properly.");
return;
}
if (!config.getGpsConfig().isMapCalibrated()) {
throw new IllegalStateException("Map is not calibrated.");
}
Point point = new Point();
getGpsConfig().getCalibration().translate(location, point);
point.set((int) (point.x * getScale()), (int) (point.y * getScale()));
jumpTo(point);
}
/**
* Scrolls the map to specific location.
*
* @param location
* - instance of Point. The coordinates of the point should be
* set in pixels in map coordinate system.
*/
public void jumpTo(Point location) {
jumpTo(location.x, location.y);
doCorrectPosition(false, false);
}
/**
* Scrolls map to specific location
*
* @param x
* - x coordinate in map coordinate system.
* @param y
* - y coordinate in map coordinate system.
*/
public void jumpTo(int x, int y) {
int width = getWidth();
int height = getHeight();
scrollTo((int) (x * getScale() - width / 2),
(int) (y * getScale() - height / 2));
doCorrectPosition(false, false);
}
/**
* Scrolls the map to specific location using scroll animation.
*
* @param location
* - instance of {@link android.location.Location}.
* @throws IllegalStateException
* if map is not calibrated. For more details see
* MapWidget.setShowMyPosition().
*/
public void scrollMapTo(Location location) {
if (config == null) {
Log.w(TAG, "Jump to skipped. Map is not initialized properly.");
return;
}
if (!config.getGpsConfig().isMapCalibrated()) {
throw new IllegalStateException("Map is not calibrated.");
}
Point point = new Point();
getGpsConfig().getCalibration().translate(location, point);
scrollMapTo(point.x, point.y);
}
/**
* Scrolls the map to specific location using scroll animation.
*
* @param location
* - instance of {@link android.graphics.Point}. Coordinates of
* the point should be set in pixels in map coordinates.
*/
public void scrollMapTo(Point location) {
scrollMapTo(location.x, location.y);
}
/**
* Scrolls the map to specific location using scroll animation.
*
* @param x
* - x coordinate of the point in map coordinates.
* @param y
* - y coordinate of the point in map coordinates.
*/
public void scrollMapTo(int x, int y) {
if (!isAnimationEnabled) {
jumpTo(x, y);
return;
}
int viewWidth = getWidth();
int viewHeight = getHeight();
if (isLayoutRequested())
return;
int mapHeight = getMapHeight();
int mapWidth = getMapWidth();
float mapScale = getScale();
int scrollX = getScrollX();
int scrollY = getScrollY();
int newX = (int) (x * mapScale) - viewWidth / 2;
int newY = (int) (y * mapScale) - viewHeight / 2;
if (viewHeight < mapHeight && newY + viewHeight > mapHeight) {
newY -= (newY + viewHeight - mapHeight);
}
if (mapWidth > viewWidth && newX + viewWidth > mapWidth) {
newX -= (newX + viewWidth - mapWidth);
}
if (newX < 0) {
newX = 0;
}
if (newY < 0) {
newY = 0;
}
if (viewHeight > mapHeight)
newY = scrollY;
if (viewWidth > mapWidth) {
newX = scrollX;
}
scroller.abortAnimation();
scroller.startScroll(scrollX, scrollY, newX - scrollX, newY - scrollY,
500);
invalidate();
}
static MapTouchedEvent mapTouchedEvent = new MapTouchedEvent();
private class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
boolean result = false;
if (onDoubleTapListener != null) {
updateMapTouchedEvent(e);
result = onDoubleTapListener.onDoubleTap(MapWidget.this, mapTouchedEvent);
}
if (result == false) {
float pivotX = e.getX();
float pivotY = e.getY();
zoomIn((int) pivotX, (int) pivotY);
result = true;
}
return result;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (config == null) {
Log.w(TAG, "Jump to skipped. Map is not initialized properly.");
return false;
}
if (!config.isFlingEnabled() || isZooming) {
return false;
}
int speed = 800;
if (Math.abs(velocityX) > speed) {
if (velocityX > 0) {
velocityX = speed;
} else {
velocityX = -speed;
}
}
if (Math.abs(velocityY) > speed) {
if (velocityY > 0) {
velocityY = speed;
} else {
velocityY = -speed;
}
}
int minX = 0;
int minY = 0;
int maxX = 0;
int maxY = 0;
if (config.isMapCenteringEnabled()) {
minX = (getWidth() - getMapWidth()) / 2;
minY = (getHeight() - getMapHeight()) / 2;
maxX = (int) getMapWidth() - getWidth();
maxY = (int) getMapHeight() - getHeight();
if (minX < 0) {
minX = 0;
}
if (minY < 0) {
minY = 0;
}
minX *= -1;
minY *= -1;
} else {
minX = -getMapWidth();
minY = -getMapHeight();
maxX = getMapWidth();
maxY = getMapHeight();
if (minX > -getWidth()) {
minX = -getWidth();
}
if (minY > -getHeight()) {
minY = -getHeight();
}
if (maxY < getHeight()) {
maxY = getHeight();
}
if (maxX < getWidth()) {
maxX = getWidth();
}
}
scroller.fling(getScrollX(), getScrollY(), -(int) (velocityX),
-(int) (velocityY), minX, // MinX
maxX, // MaxX
minY, // MinY
maxY); // MaxY
invalidate();
return true;
}
@Override
public boolean onDown(MotionEvent e) {
if (!scroller.isFinished()) { // is flinging
scroller.forceFinished(true); // to stop flinging on touch
}
return true; // else won't work
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
if (mode != Mode.NONE || isZooming)
return false;
scrollBy((int) distanceX, (int) distanceY);
// invalidate();
return true;
}
public boolean onSingleTapConfirmed(MotionEvent event) {
if (mapTouchListener != null) {
{
updateMapTouchedEvent(event);
if (debugEnabled) {
lastTouchedRect = new RectF(
translateXToMapCoordinate(event.getX()),
translateYToMapCoordinate(event.getY()),
translateXToMapCoordinate(event.getX()) + 10,
translateYToMapCoordinate(event.getY() + 10));
}
mapTouchListener.onTouch(MapWidget.this, mapTouchedEvent);
}
}
return false;
}
@Override
public void onLongPress(MotionEvent e)
{
if (longClickListener != null) {
longClickListener.onLongClick(MapWidget.this);
}
if (mapLongClicklistener != null) {
updateMapTouchedEvent(e);
mapLongClicklistener.onLongClick(MapWidget.this, mapTouchedEvent);
}
};
}
/**
* Saves mapWidget internal state, that can be restored from onCreate();
*
* @param bundle
*/
public void saveState(Bundle bundle) {
float currPosOnMapX = translateXToMapCoordinate(getWidth() / 2.0f);
float currPosOnMapY = translateYToMapCoordinate(getHeight() / 2.0f);
bundle.putFloat("com.ls.curPosOnMapX", currPosOnMapX);
bundle.putFloat("com.ls.curPosOnMapY", currPosOnMapY);
if (grid != null) {
bundle.putInt("com.ls.zoomLevel", grid.getZoomLevel());
}
bundle.putFloat("com.ls.scale", scale);
Log.d("MapWidget", "Saved point pos: [" + currPosOnMapX + ", "
+ currPosOnMapY + " ]");
}
/**
* Centers the map.
*/
public void centerMap() {
int width = getWidth();
int height = getHeight();
if (width == 0 || height == 0) {
requestCenterMap = true;
} else {
doCorrectPosition(true, isAnimationEnabled);
jumpTo(getOriginalMapWidth() / 2, getOriginalMapHeight() / 2);
}
}
private void updateMapTouchedEvent(MotionEvent event)
{
ArrayList<ObjectTouchEvent> touchedElementIds = getTouchedElementIds(
(int) event.getX() + getScrollX(),
(int) event.getY() + getScrollY());
mapTouchedEvent.setScreenX((int) event.getX());
mapTouchedEvent.setScreenY((int) event.getY());
mapTouchedEvent
.setMapX((int) translateXToMapCoordinate(event
.getX())); // X coordinate in map's
// coordinates
mapTouchedEvent
.setMapY((int) translateYToMapCoordinate(event
.getY())); // Y coordinate in map's
// coordinates
mapTouchedEvent.setTouchedObjectEvents(touchedElementIds);
}
}