refactor: extract MarkerRenderer from ItemizedLayer

- move ItemizedLayer stuff to MarkerLayer
- rename ItemizedIconLayer to ItemizedLayer
This commit is contained in:
Hannes Janetzek 2014-02-08 17:29:29 +01:00
parent 7f64fff46d
commit 5ef8026ac4
8 changed files with 465 additions and 511 deletions

@ -1 +1 @@
Subproject commit 7f3da3fb6e7e8d40280e944ad1fa3bf0504cb4b2 Subproject commit b0e4f6b7579b22d5b01fe14ebb1b79b9c6f35b8f

View File

@ -22,8 +22,8 @@ import java.util.List;
import org.oscim.android.canvas.AndroidGraphics; import org.oscim.android.canvas.AndroidGraphics;
import org.oscim.core.GeoPoint; import org.oscim.core.GeoPoint;
import org.oscim.layers.TileGridLayer; import org.oscim.layers.TileGridLayer;
import org.oscim.layers.marker.ItemizedIconLayer; import org.oscim.layers.marker.ItemizedLayer;
import org.oscim.layers.marker.ItemizedIconLayer.OnItemGestureListener; import org.oscim.layers.marker.ItemizedLayer.OnItemGestureListener;
import org.oscim.layers.marker.MarkerItem; import org.oscim.layers.marker.MarkerItem;
import org.oscim.layers.marker.MarkerItem.HotspotPlace; import org.oscim.layers.marker.MarkerItem.HotspotPlace;
import org.oscim.layers.marker.MarkerSymbol; import org.oscim.layers.marker.MarkerSymbol;
@ -47,8 +47,8 @@ implements OnItemGestureListener<MarkerItem> {
Drawable d = getResources().getDrawable(R.drawable.marker_poi); Drawable d = getResources().getDrawable(R.drawable.marker_poi);
MarkerSymbol symbol = AndroidGraphics.makeMarker(d, HotspotPlace.CENTER); MarkerSymbol symbol = AndroidGraphics.makeMarker(d, HotspotPlace.CENTER);
ItemizedIconLayer<MarkerItem> markerLayer = ItemizedLayer<MarkerItem> markerLayer =
new ItemizedIconLayer<MarkerItem>(mMap, new ArrayList<MarkerItem>(), new ItemizedLayer<MarkerItem>(mMap, new ArrayList<MarkerItem>(),
symbol, this); symbol, this);
mMap.getLayers().add(markerLayer); mMap.getLayers().add(markerLayer);

View File

@ -53,4 +53,8 @@ public abstract class Layer {
*/ */
public void onDetach() { public void onDetach() {
} }
public Map map() {
return mMap;
}
} }

View File

@ -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;
}
}

View File

@ -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 * Copyright 2013 Hannes Janetzek
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * 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 * 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/>. * this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.oscim.layers.marker; package org.oscim.layers.marker;
// TODO import java.util.List;
// - need to sort items back to front for rendering
// - and to make this work for multiple overlays
// a global scenegraph is probably required.
import org.oscim.core.MapPosition; import org.oscim.core.BoundingBox;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Point; 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.map.Map;
import org.oscim.renderer.ElementRenderer; import org.oscim.map.Viewport;
import org.oscim.renderer.MapRenderer.Matrices;
import org.oscim.renderer.elements.SymbolItem;
import org.oscim.renderer.elements.SymbolLayer;
import org.oscim.utils.GeometryUtils;
/* @author Marc Kurtz public class ItemizedLayer<Item extends MarkerItem> extends MarkerLayer<Item>
* @author Nicolas Gramlich implements GestureListener {
* @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 {
//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 */ public ItemizedLayer(Map map, List<Item> list,
protected int mExtents = 100; MarkerSymbol defaultMarker,
OnItemGestureListener<Item> onItemGestureListener) {
class InternalItem { super(map, defaultMarker);
InternalItem next;
Item item; mItemList = list;
boolean visible; mOnItemGestureListener = onItemGestureListener;
boolean changes; populate();
float x, y;
double px, py;
}
/* package */InternalItem mItems;
/* package */Object lock = new Object();
/* package */Item mFocusedItem;
/* package */boolean mUpdate;
private int mSize;
class ItemOverlay extends ElementRenderer {
private final SymbolLayer mSymbolLayer;
private final float[] mBox = new float[8];
public ItemOverlay() {
mSymbolLayer = new SymbolLayer();
}
// note: this is called from GL-Thread. so check your syncs!
@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);
}
}
mSymbolLayer.prepare();
layers.setTextureLayers(mSymbolLayer);
compile();
} }
@Override @Override
public synchronized void compile() { protected Item createItem(int index) {
super.compile(); 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;
}
/** /**
* Method by which subclasses create the actual Items. This will only be * Each of these methods performs a item sensitive check. If the item is
* called from populate() we'll cache them for later use. * 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));
}
};
// @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 i * @return true if event is handled false otherwise
* ...
* @return ...
*/ */
protected abstract Item createItem(int i); protected boolean activateSelectedItems(MotionEvent event, ActiveItem task) {
int size = mItemList.size();
if (size == 0)
return false;
/** int eventX = (int) event.getX() - mMap.getWidth() / 2;
* The number of items in this overlay. int eventY = (int) event.getY() - mMap.getHeight() / 2;
* Viewport mapPosition = mMap.getViewport();
* @return ...
*/
public abstract int size();
public ItemizedLayer(Map map, MarkerSymbol defaultSymbol) { BoundingBox bbox = mapPosition.getViewBox();
super(map);
mDefaultMarker = defaultSymbol; int nearest = -1;
mRenderer = new ItemOverlay();
/* 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;
} }
private final Point mMapPoint = new Point(); if (nearest >= 0 && task.run(nearest))
return true;
/** return false;
* 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;
}
} }
/** /**
* Returns the Item at the given index. * 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
* @param position * handled.
* the position of the item to return
* @return the Item of the given index.
*/ */
public final Item getItem(final int position) { public static interface OnItemGestureListener<T> {
synchronized (lock) { public boolean onItemSingleTapUp(int index, T item);
InternalItem item = mItems;
for (int i = mSize - position - 1; i > 0 && item != null; i--)
item = item.next;
if (item != null) public boolean onItemLongPress(int index, T item);
return item.item;
return null;
}
} }
/** public static interface ActiveItem {
* TODO public boolean run(int aIndex);
* 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;
} }
/** @Override
* @return the currently-focused item, or null if no item is currently public boolean onGesture(Gesture g, MotionEvent e) {
* focused. if (g instanceof Gesture.Tap)
*/ return activateSelectedItems(e, mActiveItemSingleTap);
public Item getFocus() {
return mFocusedItem;
}
return false;
}
} }

View File

@ -1,6 +1,10 @@
/* /*
* Copyright 2012 osmdroid authors * Copyright 2012 osmdroid authors:
* Copyright 2013 Hannes Janetzek * 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). * 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/>. * 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; package org.oscim.layers.marker;
import org.oscim.core.GeoPoint; import org.oscim.core.GeoPoint;
/** /**
* Immutable class describing a GeoPoint with a Title and a Description. * Immutable class describing a GeoPoint with a Title and a Description.
*
* @author Nicolas Gramlich
* @author Theodore Hong
* @author Fred Eisele
*/ */
public class MarkerItem { public class MarkerItem {

View File

@ -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 * Copyright 2013 Hannes Janetzek
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * 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.layers.Layer;
import org.oscim.map.Map; 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); 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 * Interface definition for overlays that contain items that can be snapped
* to (for example, when the user invokes a zoom, this could be called * to (for example, when the user invokes a zoom, this could be called
* allowing the user to snap the zoom to an interesting point.) * allowing the user to snap the zoom to an interesting point.)
*
*/ */
public interface Snappable { public interface Snappable {

View File

@ -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;
// }
// }
}