diff --git a/vtm-android-app b/vtm-android-app index 7f3da3fb..b0e4f6b7 160000 --- a/vtm-android-app +++ b/vtm-android-app @@ -1 +1 @@ -Subproject commit 7f3da3fb6e7e8d40280e944ad1fa3bf0504cb4b2 +Subproject commit b0e4f6b7579b22d5b01fe14ebb1b79b9c6f35b8f diff --git a/vtm-android-example/src/org/oscim/android/test/MarkerOverlayActivity.java b/vtm-android-example/src/org/oscim/android/test/MarkerOverlayActivity.java index bde28372..db2ab5da 100644 --- a/vtm-android-example/src/org/oscim/android/test/MarkerOverlayActivity.java +++ b/vtm-android-example/src/org/oscim/android/test/MarkerOverlayActivity.java @@ -22,8 +22,8 @@ import java.util.List; import org.oscim.android.canvas.AndroidGraphics; import org.oscim.core.GeoPoint; import org.oscim.layers.TileGridLayer; -import org.oscim.layers.marker.ItemizedIconLayer; -import org.oscim.layers.marker.ItemizedIconLayer.OnItemGestureListener; +import org.oscim.layers.marker.ItemizedLayer; +import org.oscim.layers.marker.ItemizedLayer.OnItemGestureListener; import org.oscim.layers.marker.MarkerItem; import org.oscim.layers.marker.MarkerItem.HotspotPlace; import org.oscim.layers.marker.MarkerSymbol; @@ -47,8 +47,8 @@ implements OnItemGestureListener<MarkerItem> { Drawable d = getResources().getDrawable(R.drawable.marker_poi); MarkerSymbol symbol = AndroidGraphics.makeMarker(d, HotspotPlace.CENTER); - ItemizedIconLayer<MarkerItem> markerLayer = - new ItemizedIconLayer<MarkerItem>(mMap, new ArrayList<MarkerItem>(), + ItemizedLayer<MarkerItem> markerLayer = + new ItemizedLayer<MarkerItem>(mMap, new ArrayList<MarkerItem>(), symbol, this); mMap.getLayers().add(markerLayer); diff --git a/vtm/src/org/oscim/layers/Layer.java b/vtm/src/org/oscim/layers/Layer.java index 405eff15..8ff7b7b5 100644 --- a/vtm/src/org/oscim/layers/Layer.java +++ b/vtm/src/org/oscim/layers/Layer.java @@ -53,4 +53,8 @@ public abstract class Layer { */ public void onDetach() { } + + public Map map() { + return mMap; + } } diff --git a/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java b/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java deleted file mode 100644 index ae026cd8..00000000 --- a/vtm/src/org/oscim/layers/marker/ItemizedIconLayer.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2012 osmdroid authors - * 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.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; - -public class ItemizedIconLayer<Item extends MarkerItem> extends ItemizedLayer<Item> - implements GestureListener { - //static final Logger log = LoggerFactory.getLogger(ItemizedIconOverlay.class); - - protected final List<Item> mItemList; - protected OnItemGestureListener<Item> mOnItemGestureListener; - private int mDrawnItemsLimit = Integer.MAX_VALUE; - - private final Point mTmpPoint = new Point(); - - public ItemizedIconLayer(Map map, List<Item> list, - MarkerSymbol defaultMarker, - OnItemGestureListener<Item> onItemGestureListener) { - - super(map, defaultMarker); - - mItemList = list; - mOnItemGestureListener = onItemGestureListener; - populate(); - } - - @Override - public boolean onSnapToItem(int x, int y, Point snapPoint) { - // TODO Implement this! - return false; - } - - @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 ItemizedIconLayer<Item> that = ItemizedIconLayer.this; - if (that.mOnItemGestureListener == null) { - return false; - } - return onSingleTapUpHelper(index, that.mItemList.get(index)); - } - }; - - // @Override - // public boolean onLongPress(MotionEvent event, MapPosition pos) { - // return activateSelectedItems(event, mActiveItemLongPress); - // } - - // 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 ItemizedIconLayer<Item> that = ItemizedIconLayer.this; - // if (that.mOnItemGestureListener == null) { - // return false; - // } - // return onLongPressHelper(index, getItem(index)); - // } - // }; - - // @Override - // public boolean onPress(MotionEvent e, MapPosition pos) { - // return false; - // } - - /** - * 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. - * - * @param event - * ... - * @param task - * .. - * @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.getViewport(); - - BoundingBox bbox = mapPosition.getViewBox(); - - int nearest = -1; - double dist = Double.MAX_VALUE; - - for (int i = 0; i < size; i++) { - Item item = getItem(i); - - if (!bbox.contains(item.mGeoPoint)) - continue; - - // TODO use intermediate projection - mapPosition.toScreenPoint(item.getPoint(), mTmpPoint); - - float dx = (float) (mTmpPoint.x - eventX); - float dy = (float) (mTmpPoint.y - eventY); - - // squared dist: 50*50 pixel - double d = dx * dx + dy * dy; - if (d < 2500) { - if (d < dist) { - dist = d; - nearest = i; - } - } - } - - if (nearest >= 0 && task.run(nearest)) { - return true; - } - - return false; - } - - public int getDrawnItemsLimit() { - return this.mDrawnItemsLimit; - } - - public void setDrawnItemsLimit(final int aLimit) { - this.mDrawnItemsLimit = aLimit; - } - - /** - * 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. - * - * @param <T> - * .... - */ - 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); - - return false; - } -} diff --git a/vtm/src/org/oscim/layers/marker/ItemizedLayer.java b/vtm/src/org/oscim/layers/marker/ItemizedLayer.java index e7bebbe2..36c81848 100644 --- a/vtm/src/org/oscim/layers/marker/ItemizedLayer.java +++ b/vtm/src/org/oscim/layers/marker/ItemizedLayer.java @@ -1,5 +1,9 @@ /* - * Copyright 2012 osmdroid authors + * Copyright 2012 osmdroid authors: + * Copyright 2012 Nicolas Gramlich + * Copyright 2012 Theodore Hong + * Copyright 2012 Fred Eisele + * * Copyright 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). @@ -15,287 +19,208 @@ * 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; -// TODO -// - need to sort items back to front for rendering -// - and to make this work for multiple overlays -// a global scenegraph is probably required. +import java.util.List; -import org.oscim.core.MapPosition; -import org.oscim.core.MercatorProjection; +import org.oscim.core.BoundingBox; import org.oscim.core.Point; -import org.oscim.core.Tile; +import org.oscim.event.Gesture; +import org.oscim.event.GestureListener; +import org.oscim.event.MotionEvent; import org.oscim.map.Map; -import org.oscim.renderer.ElementRenderer; -import org.oscim.renderer.MapRenderer.Matrices; -import org.oscim.renderer.elements.SymbolItem; -import org.oscim.renderer.elements.SymbolLayer; -import org.oscim.utils.GeometryUtils; +import org.oscim.map.Viewport; -/* @author Marc Kurtz - * @author Nicolas Gramlich - * @author Theodore Hong - * @author Fred Eisele - * @author Hannes Janetzek - * */ -/** - * Draws a list of {@link MarkerItem} as markers to a map. The item with the - * lowest index is drawn as last and therefore the 'topmost' marker. It also - * gets checked for onTap first. This class is generic, because you then you get - * your custom item-class passed back in onTap(). - * - * @param <Item> - * ... - */ -public abstract class ItemizedLayer<Item extends MarkerItem> extends MarkerLayer implements - MarkerLayer.Snappable { +public class ItemizedLayer<Item extends MarkerItem> extends MarkerLayer<Item> + implements GestureListener { - //static final Logger log = LoggerFactory.getLogger(ItemizedOverlay.class); + //static final Logger log = LoggerFactory.getLogger(ItemizedIconOverlay.class); - protected final MarkerSymbol mDefaultMarker; + protected final List<Item> mItemList; + protected final Point mTmpPoint = new Point(); + protected OnItemGestureListener<Item> mOnItemGestureListener; + protected int mDrawnItemsLimit = Integer.MAX_VALUE; - /** increase view to show items that are partially visible */ - protected int mExtents = 100; + public ItemizedLayer(Map map, List<Item> list, + MarkerSymbol defaultMarker, + OnItemGestureListener<Item> onItemGestureListener) { - class InternalItem { - InternalItem next; + super(map, defaultMarker); - Item item; - boolean visible; - boolean changes; - float x, y; - double px, py; + mItemList = list; + mOnItemGestureListener = onItemGestureListener; + populate(); } - /* package */InternalItem mItems; - /* package */Object lock = new Object(); - /* package */Item mFocusedItem; - /* package */boolean mUpdate; + @Override + protected Item createItem(int index) { + return mItemList.get(index); + } - private int mSize; + @Override + public int size() { + return Math.min(mItemList.size(), mDrawnItemsLimit); + } - class ItemOverlay extends ElementRenderer { + public boolean addItem(Item item) { + final boolean result = mItemList.add(item); + populate(); + return result; + } - private final SymbolLayer mSymbolLayer; - private final float[] mBox = new float[8]; + public void addItem(int location, Item item) { + mItemList.add(location, item); + } - public ItemOverlay() { - mSymbolLayer = new SymbolLayer(); + 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(); } + } - // note: this is called from GL-Thread. so check your syncs! + 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 synchronized void update(MapPosition pos, boolean changed, Matrices m) { - - if (!changed && !mUpdate) - return; - - mUpdate = false; - - double mx = pos.x; - double my = pos.y; - double scale = Tile.SIZE * pos.scale; - - int changesInvisible = 0; - int changedVisible = 0; - int numVisible = 0; - - mMap.getViewport().getMapExtents(mBox, mExtents); - - long flip = (long) (Tile.SIZE * pos.scale) >> 1; - - synchronized (lock) { - if (mItems == null) { - if (layers.getTextureLayers() != null) { - layers.clear(); - compile(); - } - return; - } - - // check visibility - for (InternalItem it = mItems; it != null; it = it.next) { - it.changes = false; - it.x = (float) ((it.px - mx) * scale); - it.y = (float) ((it.py - my) * scale); - - if (it.x > flip) - it.x -= (flip << 1); - else if (it.x < -flip) - it.x += (flip << 1); - - if (!GeometryUtils.pointInPoly(it.x, it.y, mBox, 8, 0)) { - if (it.visible) { - it.changes = true; - changesInvisible++; - } - continue; - } - if (!it.visible) { - it.visible = true; - changedVisible++; - } - numVisible++; - } - - //log.debug(numVisible + " " + changedVisible + " " + changesInvisible); - - // only update when zoomlevel changed, new items are visible - // or more than 10 of the current items became invisible - if ((numVisible == 0) && (changedVisible == 0 && changesInvisible < 10)) - return; - - // keep position for current state - // updateMapPosition(); - mMapPosition.copy(pos); - - layers.clear(); - - for (InternalItem it = mItems; it != null; it = it.next) { - if (!it.visible) - continue; - - if (it.changes) { - it.visible = false; - continue; - } - - MarkerSymbol marker = it.item.getMarker(); - if (marker == null) - marker = mDefaultMarker; - - SymbolItem s = SymbolItem.pool.get(); - s.bitmap = marker.getBitmap(); - - s.x = it.x; - s.y = it.y; - s.offset = marker.getHotspot(); - s.billboard = true; - - mSymbolLayer.addSymbol(s); - } + public boolean run(int index) { + final ItemizedLayer<Item> that = ItemizedLayer.this; + if (mOnItemGestureListener == null) { + return false; } - mSymbolLayer.prepare(); - layers.setTextureLayers(mSymbolLayer); + return onSingleTapUpHelper(index, that.mItemList.get(index)); + } + }; - compile(); + // @Override + // public boolean onLongPress(MotionEvent event, MapPosition pos) { + // return activateSelectedItems(event, mActiveItemLongPress); + // } + + // 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 ItemizedIconLayer<Item> that = ItemizedIconLayer.this; + // if (that.mOnItemGestureListener == null) { + // return false; + // } + // return onLongPressHelper(index, getItem(index)); + // } + // }; + + // @Override + // public boolean onPress(MotionEvent e, MapPosition pos) { + // return false; + // } + + /** + * 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.getViewport(); + + BoundingBox bbox = mapPosition.getViewBox(); + + int nearest = -1; + + /* squared dist: 50*50 pixel */ + double dist = 2500; + + for (int i = 0; i < size; i++) { + Item item = mItemList.get(i); + + if (!bbox.contains(item.mGeoPoint)) + continue; + + mapPosition.toScreenPoint(item.getPoint(), mTmpPoint); + + float dx = (float) (mTmpPoint.x - eventX); + float dy = (float) (mTmpPoint.y - eventY); + + double d = dx * dx + dy * dy; + if (d > dist) + continue; + + dist = d; + nearest = i; } - @Override - public synchronized void compile() { - super.compile(); - } + if (nearest >= 0 && task.run(nearest)) + return true; + + return false; } /** - * Method by which subclasses create the actual Items. This will only be - * called from populate() we'll cache them for later use. - * - * @param i - * ... - * @return ... + * 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. */ - protected abstract Item createItem(int i); + public static interface OnItemGestureListener<T> { + public boolean onItemSingleTapUp(int index, T item); - /** - * The number of items in this overlay. - * - * @return ... - */ - public abstract int size(); - - public ItemizedLayer(Map map, MarkerSymbol defaultSymbol) { - super(map); - - mDefaultMarker = defaultSymbol; - mRenderer = new ItemOverlay(); + public boolean onItemLongPress(int index, T item); } - private final Point mMapPoint = new Point(); - - /** - * Utility method to perform all processing on a new ItemizedOverlay. - * Subclasses provide Items through the createItem(int) method. The subclass - * should call this as soon as it has data, before anything else gets - * called. - */ - protected final void populate() { - synchronized (lock) { - final int size = size(); - mSize = size; - - // reuse previous items - InternalItem pool = mItems; - mItems = null; - - // flip order to draw in backward cycle, so the items - // with the least index are on the front. - for (int a = 0; a < size; a++) { - InternalItem it; - if (pool != null) { - it = pool; - it.visible = false; - it.changes = false; - pool = pool.next; - } else { - it = new InternalItem(); - } - it.next = mItems; - mItems = it; - - it.item = createItem(a); - - // pre-project points - MercatorProjection.project(it.item.getPoint(), mMapPoint); - it.px = mMapPoint.x; - it.py = mMapPoint.y; - } - mUpdate = true; - } + public static interface ActiveItem { + public boolean run(int aIndex); } - /** - * Returns the Item at the given index. - * - * @param position - * the position of the item to return - * @return the Item of the given index. - */ - public final Item getItem(final int position) { - synchronized (lock) { - InternalItem item = mItems; - for (int i = mSize - position - 1; i > 0 && item != null; i--) - item = item.next; + @Override + public boolean onGesture(Gesture g, MotionEvent e) { + if (g instanceof Gesture.Tap) + return activateSelectedItems(e, mActiveItemSingleTap); - if (item != null) - return item.item; - - return null; - } + return false; } - - /** - * TODO - * If the given Item is found in the overlay, force it to be the current - * focus-bearer. Any registered {link ItemizedLayer#OnFocusChangeListener} - * will be notified. This does not move the map, so if the Item isn't - * already centered, the user may get confused. If the Item is not found, - * this is a no-op. You can also pass null to remove focus. - * - * @param item - */ - public void setFocus(final Item item) { - mFocusedItem = item; - } - - /** - * @return the currently-focused item, or null if no item is currently - * focused. - */ - public Item getFocus() { - return mFocusedItem; - } - } diff --git a/vtm/src/org/oscim/layers/marker/MarkerItem.java b/vtm/src/org/oscim/layers/marker/MarkerItem.java index d755d424..79fe8c4a 100644 --- a/vtm/src/org/oscim/layers/marker/MarkerItem.java +++ b/vtm/src/org/oscim/layers/marker/MarkerItem.java @@ -1,6 +1,10 @@ /* - * Copyright 2012 osmdroid authors - * Copyright 2013 Hannes Janetzek + * Copyright 2012 osmdroid authors: + * Copyright 2012 Nicolas Gramlich + * Copyright 2012 Theodore Hong + * Copyright 2012 Fred Eisele + * + * Copyright 2014 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -16,17 +20,12 @@ * this program. If not, see <http://www.gnu.org/licenses/>. */ -// Created by plusminus on 00:02:58 - 03.10.2008 package org.oscim.layers.marker; import org.oscim.core.GeoPoint; /** * Immutable class describing a GeoPoint with a Title and a Description. - * - * @author Nicolas Gramlich - * @author Theodore Hong - * @author Fred Eisele */ public class MarkerItem { diff --git a/vtm/src/org/oscim/layers/marker/MarkerLayer.java b/vtm/src/org/oscim/layers/marker/MarkerLayer.java index 2f9c9d84..f9a8894c 100644 --- a/vtm/src/org/oscim/layers/marker/MarkerLayer.java +++ b/vtm/src/org/oscim/layers/marker/MarkerLayer.java @@ -1,5 +1,9 @@ /* - * Copyright 2012 osmdroid authors + * Copyright 2012 osmdroid authors: + * Copyright 2012 Nicolas Gramlich + * Copyright 2012 Theodore Hong + * Copyright 2012 Fred Eisele + * * Copyright 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). @@ -22,19 +26,73 @@ import org.oscim.core.Point; import org.oscim.layers.Layer; import org.oscim.map.Map; -public abstract class MarkerLayer extends Layer { +/** + * Draws a list of {@link MarkerItem} as markers to a map. The item with the + * lowest index is drawn as last and therefore the 'topmost' marker. It also + * gets checked for onTap first. This class is generic, because you then you get + * your custom item-class passed back in onTap(). << TODO + */ +public abstract class MarkerLayer<Item extends MarkerItem> extends Layer { - public MarkerLayer(Map map) { + protected final MarkerRenderer mMarkerRenderer; + protected Item mFocusedItem; + + /** + * Method by which subclasses create the actual Items. This will only be + * called from populate() we'll cache them for later use. + */ + protected abstract Item createItem(int i); + + /** + * The number of items in this overlay. + */ + public abstract int size(); + + @SuppressWarnings("unchecked") + public MarkerLayer(Map map, MarkerSymbol defaultSymbol) { super(map); + + mMarkerRenderer = new MarkerRenderer((MarkerLayer<MarkerItem>) this, defaultSymbol); + mRenderer = mMarkerRenderer; } /** - * TBD + * Utility method to perform all processing on a new ItemizedOverlay. + * Subclasses provide Items through the createItem(int) method. The subclass + * should call this as soon as it has data, before anything else gets + * called. + */ + protected final void populate() { + mMarkerRenderer.populate(size()); + } + + /** + * TODO + * If the given Item is found in the overlay, force it to be the current + * focus-bearer. Any registered {link ItemizedLayer#OnFocusChangeListener} + * will be notified. This does not move the map, so if the Item isn't + * already centered, the user may get confused. If the Item is not found, + * this is a no-op. You can also pass null to remove focus. * + * @param item + */ + public void setFocus(Item item) { + mFocusedItem = item; + } + + /** + * @return the currently-focused item, or null if no item is currently + * focused. + */ + public Item getFocus() { + return mFocusedItem; + } + + /** + * TODO * Interface definition for overlays that contain items that can be snapped * to (for example, when the user invokes a zoom, this could be called * allowing the user to snap the zoom to an interesting point.) - * */ public interface Snappable { diff --git a/vtm/src/org/oscim/layers/marker/MarkerRenderer.java b/vtm/src/org/oscim/layers/marker/MarkerRenderer.java new file mode 100644 index 00000000..b35b4c16 --- /dev/null +++ b/vtm/src/org/oscim/layers/marker/MarkerRenderer.java @@ -0,0 +1,213 @@ +/* + * 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 org.oscim.core.MapPosition; +import org.oscim.core.MercatorProjection; +import org.oscim.core.Point; +import org.oscim.core.Tile; +import org.oscim.map.Map; +import org.oscim.renderer.ElementRenderer; +import org.oscim.renderer.MapRenderer.Matrices; +import org.oscim.renderer.elements.SymbolItem; +import org.oscim.renderer.elements.SymbolLayer; +import org.oscim.utils.GeometryUtils; +import org.oscim.utils.pool.Inlist; + +//TODO +//- need to sort items back to front for rendering +//- and to make this work for multiple overlays +//a global scenegraph is probably required. + +public class MarkerRenderer extends ElementRenderer { + + protected final MarkerSymbol mDefaultMarker; + + private final SymbolLayer mSymbolLayer; + private final float[] mBox = new float[8]; + private final MarkerLayer<MarkerItem> mMarkerLayer; + /** increase view to show items that are partially visible */ + protected int mExtents = 100; + private boolean mUpdate; + private Map mMap; + private InternalItem mItems; + private final Point mMapPoint = new Point(); + + static class InternalItem extends Inlist<InternalItem> { + MarkerItem item; + boolean visible; + boolean changes; + float x, y; + double px, py; + } + + public MarkerRenderer(MarkerLayer<MarkerItem> markerLayer, MarkerSymbol defaultSymbol) { + mSymbolLayer = new SymbolLayer(); + mMarkerLayer = markerLayer; + mDefaultMarker = defaultSymbol; + } + + @Override + public synchronized void update(MapPosition pos, boolean changed, Matrices m) { + + if (!changed && !mUpdate) + return; + + mUpdate = false; + + double mx = pos.x; + double my = pos.y; + double scale = Tile.SIZE * pos.scale; + + int changesInvisible = 0; + int changedVisible = 0; + int numVisible = 0; + + mMarkerLayer.map().getViewport().getMapExtents(mBox, mExtents); + + long flip = (long) (Tile.SIZE * pos.scale) >> 1; + + if (mItems == null) { + if (layers.getTextureLayers() != null) { + layers.clear(); + compile(); + } + return; + } + + /* check visibility */ + for (InternalItem it = mItems; it != null; it = it.next) { + it.changes = false; + it.x = (float) ((it.px - mx) * scale); + it.y = (float) ((it.py - my) * scale); + + if (it.x > flip) + it.x -= (flip << 1); + else if (it.x < -flip) + it.x += (flip << 1); + + if (!GeometryUtils.pointInPoly(it.x, it.y, mBox, 8, 0)) { + if (it.visible) { + it.changes = true; + changesInvisible++; + } + continue; + } + if (!it.visible) { + it.visible = true; + changedVisible++; + } + numVisible++; + } + + //log.debug(numVisible + " " + changedVisible + " " + changesInvisible); + + /* only update when zoomlevel changed, new items are visible + * or more than 10 of the current items became invisible */ + if ((numVisible == 0) && (changedVisible == 0 && changesInvisible < 10)) + return; + + /* keep position for current state */ + mMapPosition.copy(pos); + + layers.clear(); + + for (InternalItem it = mItems; it != null; it = it.next) { + if (!it.visible) + continue; + + if (it.changes) { + it.visible = false; + continue; + } + + MarkerSymbol marker = it.item.getMarker(); + if (marker == null) + marker = mDefaultMarker; + + SymbolItem s = SymbolItem.pool.get(); + s.bitmap = marker.getBitmap(); + + s.x = it.x; + s.y = it.y; + s.offset = marker.getHotspot(); + s.billboard = true; + + mSymbolLayer.addSymbol(s); + } + + mSymbolLayer.prepare(); + layers.setTextureLayers(mSymbolLayer); + + compile(); + } + + @Override + public synchronized void compile() { + super.compile(); + } + + protected synchronized void populate(int size) { + + InternalItem pool = mItems; + mItems = null; + + for (int i = 0; i < size; i++) { + InternalItem it; + if (pool != null) { + it = pool; + it.visible = false; + it.changes = false; + pool = pool.next; + } else { + it = new InternalItem(); + } + it.next = mItems; + mItems = it; + + it.item = mMarkerLayer.createItem(i); + + /* pre-project points */ + MercatorProjection.project(it.item.getPoint(), mMapPoint); + it.px = mMapPoint.x; + it.py = mMapPoint.y; + } + mUpdate = true; + } + + // /** + // * Returns the Item at the given index. + // * + // * @param position + // * the position of the item to return + // * @return the Item of the given index. + // */ + // public final Item getItem(int position) { + // + // synchronized (lock) { + // InternalItem item = mItems; + // for (int i = mSize - position - 1; i > 0 && item != null; i--) + // item = item.next; + // + // if (item != null) + // return item.item; + // + // return null; + // } + // } +}