/**
* Filename: MapView.java (in org.repin.android.ui.mapview)
* This file is part of the Redpin project.
*
* Redpin 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
* any later version.
*
* Redpin 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 Redpin. If not, see <http://www.gnu.org/licenses/>.
*
* (c) Copyright ETH Zurich, Pascal Brogle, Philipp Bolliger, 2010, ALL RIGHTS RESERVED.
*
* www.redpin.org
*/
package org.redpin.android.ui.mapview;
import java.util.HashMap;
import java.util.List;
import org.redpin.android.R;
import org.redpin.android.core.Location;
import org.redpin.android.core.Map;
import org.redpin.android.db.EntityHomeFactory;
import org.redpin.android.net.DownloadImageTask;
import org.redpin.android.net.DownloadImageTask.DownloadImageTaskCallback;
import org.redpin.android.provider.RedpinContract;
import org.redpin.android.ui.mapview.ZoomAndScrollImageView.ZoomAndScrollViewListener;
import android.app.AlertDialog;
import android.content.ContentUris;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.TextView;
/**
* {@link MapView} displays a {@link Map} on the screen and its {@link Location}
* s depending on the selection of the user.
*
* @author Pascal Brogle (broglep@student.ethz.ch)
*
*/
public class MapView extends FrameLayout implements DownloadImageTaskCallback,
ZoomAndScrollViewListener {
private ZoomAndScrollImageView imageView;
private FrameLayout contentView;
private TextView loadingView;
private Map currentMap;
private Location currentLocation;
private HashMap<Location, LocationMarker> locationMarker = new HashMap<Location, LocationMarker>();
private LocationMarker requestedCenterMarker;
private boolean loadPending;
private int[] requestedScroll;
private Uri currentUri;
private int imageWidth;
private int imageHeight;
private final static long FADE_DURATION = 500;
private static final String TAG = MapView.class.getSimpleName();
/**
* Construct a new MapView with a Context object.
*
* @param context
* A Context object used to access application assets.
*/
public MapView(Context context) {
super(context);
initView(context);
}
/**
* Construct a new MapView with layout parameters and a default style.
*
* @param context
* A Context object used to access application assets.
* @param attrs
* An AttributeSet passed to our parent.
* @param defStyle
* The default style resource ID.
*/
public MapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
/**
* Construct a new MapView with layout parameters.
*
* @param context
* A Context object used to access application assets.
* @param attrs
* An AttributeSet passed to our parent.
*/
public MapView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
/**
* Initializes the MapView
*
* @param context
* {@link Context}
*/
protected void initView(Context context) {
imageView = new ZoomAndScrollImageView(context);
imageView.setListener(this);
addView(imageView);
contentView = new FrameLayout(context);
contentView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
addView(contentView);
loadingView = new TextView(getContext());
loadingView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
loadingView.setGravity(Gravity.CENTER);
loadingView.setTextColor(getResources().getColor(R.color.light_grey));
loadingView.setTextSize(30);
loadingView.setTypeface(Typeface.DEFAULT_BOLD);
loadingView.setText(R.string.loading_text);
loadingView.setVisibility(INVISIBLE);
loadingView.setBackgroundColor(Color.WHITE);
addView(loadingView);
}
/**
* Shows an {@link LocationMarker}
*
* @param marker
* {@link LocationMarker} to be shown
*/
private void showLocationMarker(LocationMarker marker) {
if (loadPending) {
Log
.v(TAG,
"showLocationMarker: Map image is pending, marker not shown.");
return;
}
marker.setVisibility(VISIBLE);
}
/**
* Shows all {@link LocationMarker}s
*/
private void showLocationMarkers() {
for (LocationMarker m : locationMarker.values()) {
showLocationMarker(m);
}
}
/**
*
* @return The current displayed {@link Map}
*/
public Map getCurrentMap() {
return currentMap;
}
/**
*
* @return The current estimated {@link Location}
*/
public Location getCurrentLocation() {
return currentLocation;
}
/**
*
* @return uri of the current image
*/
public String getUrl() {
if (currentUri == null)
return null;
return currentUri.toString();
}
/**
*
* @return Fade out {@link Animation}
*/
protected Animation fadeOut() {
AlphaAnimation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setDuration(FADE_DURATION);
return fadeOut;
}
/**
*
* @return Fade in {@link Animation}
*/
protected Animation fadeIn() {
AlphaAnimation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setDuration(FADE_DURATION);
return fadeIn;
}
/**
* {@inheritDoc}
*/
@Override
public boolean onTrackballEvent(MotionEvent e) {
System.out.println(e);
return false;
}
/**
* Shows the map image.
*
* @param url
* URL of the map image
*/
protected void showImage(String url) {
loadPending = true;
loadingView.startAnimation(fadeIn());
loadingView.setVisibility(VISIBLE);
//DownloadImageTask task = new DownloadImageTask(this);
//task.execute(url);
onImageDownloaded(url, Environment.getExternalStorageDirectory() + "/indoormaps/" + url);
}
/**
* {@inheritDoc}
*/
@Override
public void onImageDownloaded(String url, String path) {
loadPending = false;
Bitmap bm = BitmapFactory.decodeFile(path);
imageWidth = bm.getWidth();
imageHeight = bm.getHeight();
contentView.setLayoutParams(new LayoutParams(imageWidth, imageHeight));
/*
* ViewGroup.LayoutParams params = contentView.getLayoutParams();
* params.width = w; params.height = h;
* contentView.setLayoutParams(params);
*/
imageView.setImageBitmap(bm);
// imageView.requestFocus();
showLocationMarkers();
processRequestMarkerOnCenter();
processRequestScroll();
loadingView.startAnimation(fadeOut());
loadingView.setVisibility(INVISIBLE);
}
/**
* {@inheritDoc}
*/
@Override
public void onImageDownloadFailure(String url) {
loadPending = false;
// imageView.requestFocus();
//showLocationMarkers();
//processRequestMarkerOnCenter();
//processRequestScroll();
loadingView.startAnimation(fadeOut());
loadingView.setVisibility(INVISIBLE);
//loadPending = false;
//new AlertDialog.Builder(getContext()).setMessage(
// getContext().getString(R.string.map_download_failed)).show();
}
public int[] getScrollXY() {
float[] current = imageView.getCurrentXY();
return new int[] { -(int) current[0], -(int) current[1] };
}
/**
* {@inheritDoc}
*/
@Override
public void scrollBy(int x, int y) {
imageView.scrollBy(x, y);
}
/**
* {@inheritDoc}
*/
@Override
public void scrollTo(int x, int y) {
imageView.scrollTo(x, y);
}
/**
* Shows either {@link Map} or {@link Location} represented by an
* {@link Uri}
*
* @param uri
* {@link Uri} of the {@link Map} or {@link Location}
*/
public void show(Uri uri) {
if (uri == null)
return;
currentUri = uri;
long id = ContentUris.parseId(uri);
String type = getContext().getContentResolver().getType(uri);
if (RedpinContract.Map.ITEM_TYPE.equals(type)) {
Map m = EntityHomeFactory.getMapHome().getById(id);
showMap(m);
return;
}
if (RedpinContract.Location.ITEM_TYPE.equals(type)) {
Location l = EntityHomeFactory.getLocationHome().getById(id);
showLocation(l, false);
return;
}
}
/**
* Adds a new {@link LocationMarker} on the center of the screen and sets
* the {@link Location}s coordinates accordingly.
*
* @param newLocation
* {@link Location} for the {@link LocationMarker}
*/
public void addNewLocation(Location newLocation) {
if (newLocation == null)
return;
int w = getWidth();
int h = getHeight();
float[] point = new float[] { 0, 0 };
lastMatrix.mapPoints(point);
int offsetX = (int) ((-point[0] + (w / 2)) / lastScale);
int offsetY = (int) ((-point[1] + (h / 2)) / lastScale);
newLocation.setMapXcord(offsetX);
newLocation.setMapYcord(offsetY);
LocationMarker marker = addMarkerForLocation(newLocation);
marker.onScaleChanged(lastScale);
marker.showAnnotation();
marker.setVisibility(VISIBLE);
marker.beginEdit();
}
/**
* Add a {@link LocationMarkerAnnotation} for the specified {@link Location}
*
* @param l
* {@link Location}
* @return The added {@link LocationMarkerAnnotation}
*/
protected LocationMarker addMarkerForLocation(Location l) {
LocationMarker marker = locationMarker.get(l);
if (marker == null) {
marker = new LocationMarker(getContext(), l, contentView);
locationMarker.put(l, marker);
contentView.addView(marker);
}
if (l.equals(currentLocation)) {
marker.setIsCurrentLocation(true);
}
marker.setEnabled(modifiable);
return marker;
}
/**
* Scrolls the view so that the {@link LocationMarker} is on the center.
*
* @param marker
* {@link LocationMarker} to be centered on the screen
*/
public void centerMarker(LocationMarker marker) {
// imageView.setZoom(1.0f, true);
Location location = marker.getLocation();
int w = getMeasuredWidth();
int h = getMeasuredHeight();
int left = location.getMapXcord() - w / 2;
int top = location.getMapYcord() - h / 2;
// due to a bug javascriptScrollTo has to be used
// scrollTo(left, top);
scrollTo(left, top);
}
/**
* Centers the requested {@link LocationMarker}
*
*/
public void processRequestMarkerOnCenter() {
if (requestedCenterMarker == null) {
return;
}
centerMarker(requestedCenterMarker);
requestedCenterMarker = null;
}
/**
* Requests that a {@link LocationMarker} is centered on the screen.
*
* @param marker
* {@link LocationMarker} to be centered on the screen
*/
public void requestMarkerOnCenter(LocationMarker marker) {
if (loadPending) {
requestedCenterMarker = marker;
} else {
centerMarker(marker);
}
}
/**
* Processes pending scroll request (if any)
*/
public void processRequestScroll() {
if (requestedScroll == null) {
return;
}
scrollTo(requestedScroll[0], requestedScroll[1]);
requestedScroll = null;
}
/**
* Scrolls to requested x,y position
*
* @param x
* position
* @param y
* position
* @param forceDelay
* Whether scrolling should forced to be delayed after the page
* loading
*/
public void requestScroll(int x, int y, boolean forceDelay) {
if (loadPending || forceDelay) {
requestedScroll = new int[] { x, y };
} else {
scrollTo(x, y);
}
}
/**
* Requests that the view should be scrolled
*
* @param x
* @param y
*/
public void requestScroll(int x, int y) {
requestScroll(x, y, false);
}
/**
* Shows a {@link Location} on the {@link MapView}
*
* @param location
* The {@link Location} to be shown
* @param isCurrentLocation
* Whether the {@link Location} is the current estimated location
*/
public void showLocation(Location location, boolean isCurrentLocation) {
if (isCurrentLocation) {
currentLocation = location;
}
removeAllMarkers();
Map locMap = (Map) location.getMap();
if (!locMap.equals(currentMap)) {
currentMap = locMap;
setupMapViewImage(currentMap);
}
checkCurrentLocationMarker();
LocationMarker marker = addMarkerForLocation(location);
showLocationMarkers();
requestMarkerOnCenter(marker);
}
/**
* Setups the {@link MapView} image
*
* @param map
* {@link Map} to be shown
*/
protected void setupMapViewImage(Map map) {
showImage(map.getMapURL());
}
/**
* Shows a {@link Map}
*
* @param map
* {@link Map} to be shown
*/
public void showMap(Map map) {
if (!map.equals(currentMap)) {
currentMap = map;
Log.v(TAG, "showMap: initializing new map view");
removeAllMarkers();
setupMapViewImage(currentMap);
setupAllMarkers(currentMap);
checkCurrentLocationMarker();
} else {
Log.v(TAG, "showMap: map already shown");
List<Location> list = EntityHomeFactory.getLocationHome()
.getListByMap(map);
if (list.size() != locationMarker.size()) {
Log
.v(TAG,
"showMap: map already shown, but location number changed");
removeAllMarkers();
setupMarkers(list);
}
}
showLocationMarkers();
}
/**
* Checks if current location is on the same map and displays it if
* necessary.
*/
private void checkCurrentLocationMarker() {
if (currentLocation != null && currentLocation.getMap() != null) {
if (currentLocation.getMap().equals(currentMap)) {
if (!locationMarker.containsKey(currentLocation)) {
addMarkerForLocation(currentLocation);
}
} else {
removeMarkerForLocation(currentLocation);
}
}
}
/**
* Removes a {@link LocationMarker}.
*
* @param location
* {@link Location} of the {@link LocationMarker} that has to be
* removed
*/
private void removeMarkerForLocation(Location location) {
LocationMarker marker = locationMarker.remove(location);
if (marker == null)
return;
marker.removeFromContainer();
}
/**
* Removes all {@link LocationMarker}
*/
private void removeAllMarkers() {
for (LocationMarker m : locationMarker.values()) {
m.removeFromContainer();
}
locationMarker.clear();
}
/**
* Adds {@link LocationMarker} for a {@link List} of {@link Location}s.
*
* @param list
* {@link List} of {@link Location} to be shown
*/
private void setupMarkers(List<Location> list) {
for (Location l : list) {
addMarkerForLocation(l);
}
}
/**
* Adds {@link LocationMarker} for all {@link Location}s of a {@link Map}
*
* @param map
* {@link Map}
*/
private void setupAllMarkers(Map map) {
setupMarkers(EntityHomeFactory.getLocationHome().getListByMap(map));
}
private boolean modifiable = false;
/**
* Enables or disables the possibility to modify {@link LocationMarker}s and
* {@link LocationMarkerAnnotation}s.
*
* @param enabled
* Whether the {@link MapView} should be modifiable
*/
public void setModifiable(boolean enabled) {
if (modifiable == enabled)
return;
for (LocationMarker m : locationMarker.values()) {
m.setEnabled(enabled);
}
modifiable = enabled;
}
Matrix lastMatrix = new Matrix();
float lastScale;
/**
* {@inheritDoc}
*/
@Override
public void onMatrixChange(Matrix matrix, ZoomAndScrollImageView view) {
lastMatrix = matrix;
float[] values = new float[9];
matrix.getValues(values);
float scale = values[Matrix.MSCALE_X];
lastScale = scale;
float[] point = new float[] { 0, 0 };
matrix.mapPoints(point);
contentView.scrollTo((int) -point[0], (int) -point[1]);
// contentView.startAnimation(sa);
for (LocationMarker m : locationMarker.values()) {
m.onScaleChanged(scale);
}
ViewGroup.LayoutParams params = contentView.getLayoutParams();
params.width = (int) (imageWidth * scale);
params.height = (int) (imageHeight * scale);
contentView.setLayoutParams(params);
}
/**
* {@inheritDoc}
*/
@Override
public void onSingleTab(MotionEvent e) {
for (LocationMarker l : locationMarker.values()) {
if (l.clicked) {
l.onClick(this);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void onScaleBegin(ZoomAndScrollImageView view) {
contentView.setVisibility(INVISIBLE);
}
/**
* {@inheritDoc}
*/
@Override
public void onScaleEnd(ZoomAndScrollImageView view) {
Animation a = fadeIn();
a.setDuration(a.getDuration() / 2);
contentView.startAnimation(a);
contentView.setVisibility(VISIBLE);
}
/**
* {@inheritDoc}
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (imageView.onKeyDown(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
imageView.dispatchTrackballEvent(event);
return true;
}
/*
* @Override protected void onRestoreInstanceState(Parcelable state) { if
* (!(state instanceof SavedState)) { super.onRestoreInstanceState(state);
* return; }
*
* //currentLocation =
* EntityHomeFactory.getLocationHome().getById(ss.currentLocation);
*
* SavedState ss = (SavedState)state;
* super.onRestoreInstanceState(ss.getSuperState());
*
* if (ss.shown != null) { show(ss.shown); }
*
* requestScroll(ss.scrollX, ss.scrollY);
*
*
*
* }
*
* @Override protected Parcelable onSaveInstanceState() { Parcelable
* superState = super.onSaveInstanceState(); SavedState ss = new
* SavedState(superState); ss.shown = currentUri; ss.scrollX = getScrollX();
* ss.scrollY = getScrollY(); return ss; }
*
*
* public static class SavedState extends BaseSavedState {
*
* Uri shown; int scrollX; int scrollY;
*
*
* public SavedState(Parcelable arg0) { super(arg0); }
*
* @Override public void writeToParcel(Parcel out, int flags) {
* super.writeToParcel(out, flags); Uri.writeToParcel(out, shown);
*
* out.writeInt(scrollX); out.writeInt(scrollY);
* //out.writeLong(currentLocation); //out.writeLong(currentMap);
*
* }
*
* public static final Parcelable.Creator<SavedState> CREATOR = new
* Parcelable.Creator<SavedState>() { public SavedState
* createFromParcel(Parcel in) { return new SavedState(in); }
*
* public SavedState[] newArray(int size) { return new SavedState[size]; }
* };
*
* private SavedState(Parcel in) {
*
* super(in);
*
* shown = Uri.CREATOR.createFromParcel(in); scrollX = in.readInt(); scrollY
* = in.readInt(); //currentLocation = in.readLong(); //currentMap =
* in.readLong();
*
* }
*
*
*
* }
*/
}