/*
* Copyright 2012 osmdroid authors: Nicolas Gramlich, Theodore Hong, Fred Eisele
*
* Copyright 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.layers.marker;
import java.util.ArrayList;
import java.util.List;
import org.oscim.core.BoundingBox;
import org.oscim.core.Point;
import org.oscim.event.Gesture;
import org.oscim.event.GestureListener;
import org.oscim.event.MotionEvent;
import org.oscim.map.Map;
import org.oscim.map.Viewport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ItemizedLayer<Item extends MarkerItem> extends MarkerLayer<Item>
implements GestureListener {
static final Logger log = LoggerFactory.getLogger(ItemizedLayer.class);
protected final List<Item> mItemList;
protected final Point mTmpPoint = new Point();
protected OnItemGestureListener<Item> mOnItemGestureListener;
protected int mDrawnItemsLimit = Integer.MAX_VALUE;
public ItemizedLayer(Map map, MarkerSymbol defaulMarker) {
this(map, new ArrayList<Item>(), defaulMarker, null);
}
public ItemizedLayer(Map map, List<Item> list,
MarkerSymbol defaultMarker,
OnItemGestureListener<Item> listener) {
super(map, defaultMarker);
mItemList = list;
mOnItemGestureListener = listener;
populate();
}
public void setOnItemGestureListener(OnItemGestureListener<Item> listener) {
mOnItemGestureListener = listener;
}
@Override
protected Item createItem(int index) {
return mItemList.get(index);
}
@Override
public int size() {
return Math.min(mItemList.size(), mDrawnItemsLimit);
}
public boolean addItem(Item item) {
final boolean result = mItemList.add(item);
populate();
return result;
}
public void addItem(int location, Item item) {
mItemList.add(location, item);
}
public boolean addItems(List<Item> items) {
final boolean result = mItemList.addAll(items);
populate();
return result;
}
public void removeAllItems() {
removeAllItems(true);
}
public void removeAllItems(boolean withPopulate) {
mItemList.clear();
if (withPopulate) {
populate();
}
}
public boolean removeItem(Item item) {
final boolean result = mItemList.remove(item);
populate();
return result;
}
public Item removeItem(int position) {
final Item result = mItemList.remove(position);
populate();
return result;
}
/**
* Each of these methods performs a item sensitive check. If the item is
* located its corresponding method is called. The result of the call is
* returned. Helper methods are provided so that child classes may more
* easily override behavior without resorting to overriding the
* ItemGestureListener methods.
*/
// @Override
// public boolean onTap(MotionEvent event, MapPosition pos) {
// return activateSelectedItems(event, mActiveItemSingleTap);
// }
protected boolean onSingleTapUpHelper(int index, Item item) {
return mOnItemGestureListener.onItemSingleTapUp(index, item);
}
private final ActiveItem mActiveItemSingleTap = new ActiveItem() {
@Override
public boolean run(int index) {
final ItemizedLayer<Item> that = ItemizedLayer.this;
if (mOnItemGestureListener == null) {
return false;
}
return onSingleTapUpHelper(index, that.mItemList.get(index));
}
};
protected boolean onLongPressHelper(int index, Item item) {
return this.mOnItemGestureListener.onItemLongPress(index, item);
}
private final ActiveItem mActiveItemLongPress = new ActiveItem() {
@Override
public boolean run(final int index) {
final ItemizedLayer<Item> that = ItemizedLayer.this;
if (that.mOnItemGestureListener == null) {
return false;
}
return onLongPressHelper(index, that.mItemList.get(index));
}
};
/**
* When a content sensitive action is performed the content item needs to be
* identified. This method does that and then performs the assigned task on
* that item.
*
* @return true if event is handled false otherwise
*/
protected boolean activateSelectedItems(MotionEvent event, ActiveItem task) {
int size = mItemList.size();
if (size == 0)
return false;
int eventX = (int) event.getX() - mMap.getWidth() / 2;
int eventY = (int) event.getY() - mMap.getHeight() / 2;
Viewport mapPosition = mMap.viewport();
BoundingBox bbox = mapPosition.getBBox(128);
int nearest = -1;
int inside = -1;
double insideY = -Double.MAX_VALUE;
/* squared dist: 50*50 pixel ~ 2mm on 400dpi */
double dist = 2500;
for (int i = 0; i < size; i++) {
Item item = mItemList.get(i);
if (!bbox.contains(item.geoPoint))
continue;
mapPosition.toScreenPoint(item.getPoint(), mTmpPoint);
float dx = (float) (mTmpPoint.x - eventX);
float dy = (float) (mTmpPoint.y - eventY);
MarkerSymbol it = item.getMarker();
if (it == null)
it = mMarkerRenderer.mDefaultMarker;
if (it.isInside(dx, dy)) {
if (mTmpPoint.y > insideY) {
insideY = mTmpPoint.y;
inside = i;
}
}
if (inside >= 0)
continue;
double d = dx * dx + dy * dy;
if (d > dist)
continue;
dist = d;
nearest = i;
}
if (inside >= 0)
nearest = inside;
if (nearest >= 0 && task.run(nearest)) {
mMarkerRenderer.update();
mMap.render();
return true;
}
return false;
}
/**
* When the item is touched one of these methods may be invoked depending on
* the type of touch. Each of them returns true if the event was completely
* handled.
*/
public static interface OnItemGestureListener<T> {
public boolean onItemSingleTapUp(int index, T item);
public boolean onItemLongPress(int index, T item);
}
public static interface ActiveItem {
public boolean run(int aIndex);
}
@Override
public boolean onGesture(Gesture g, MotionEvent e) {
if (g instanceof Gesture.Tap)
return activateSelectedItems(e, mActiveItemSingleTap);
if (g instanceof Gesture.LongPress)
return activateSelectedItems(e, mActiveItemLongPress);
return false;
}
public Item getByUid(Object uid) {
for (Item it : mItemList)
if (it.getUid() == uid)
return it;
return null;
}
}