/*
* 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.Comparator;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Point;
import org.oscim.core.Tile;
import org.oscim.renderer.BucketRenderer;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.bucket.SymbolBucket;
import org.oscim.renderer.bucket.SymbolItem;
import org.oscim.utils.TimSort;
import org.oscim.utils.geom.GeometryUtils;
public class MarkerRenderer extends BucketRenderer {
protected final MarkerSymbol mDefaultMarker;
private final SymbolBucket mSymbolLayer;
private final float[] mBox = new float[8];
private final MarkerLayer<MarkerItem> mMarkerLayer;
private final Point mMapPoint = new Point();
/** increase view to show items that are partially visible */
protected int mExtents = 100;
/** flag to force update of markers */
private boolean mUpdate;
private InternalItem[] mItems;
static class InternalItem {
MarkerItem item;
boolean visible;
boolean changes;
float x, y;
double px, py;
float dy;
@Override
public String toString() {
return "\n" + x + ":" + y + " / " + dy + " " + visible;
}
}
public MarkerRenderer(MarkerLayer<MarkerItem> markerLayer, MarkerSymbol defaultSymbol) {
mSymbolLayer = new SymbolBucket();
mMarkerLayer = markerLayer;
mDefaultMarker = defaultSymbol;
}
@Override
public synchronized void update(GLViewport v) {
if (!v.changed() && !mUpdate)
return;
mUpdate = false;
double mx = v.pos.x;
double my = v.pos.y;
double scale = Tile.SIZE * v.pos.scale;
//int changesInvisible = 0;
//int changedVisible = 0;
int numVisible = 0;
mMarkerLayer.map().viewport().getMapExtents(mBox, mExtents);
long flip = (long) (Tile.SIZE * v.pos.scale) >> 1;
if (mItems == null) {
if (buckets.get() != null) {
buckets.clear();
compile();
}
return;
}
double angle = Math.toRadians(v.pos.bearing);
float cos = (float) Math.cos(angle);
float sin = (float) Math.sin(angle);
/* check visibility */
for (InternalItem it : mItems) {
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;
}
it.dy = sin * it.x + cos * it.y;
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;
buckets.clear();
if (numVisible == 0) {
compile();
return;
}
/* keep position for current state */
mMapPosition.copy(v.pos);
mMapPosition.bearing = -mMapPosition.bearing;
sort(mItems, 0, mItems.length);
//log.debug(Arrays.toString(mItems));
for (InternalItem it : mItems) {
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.set(it.x, it.y, marker.getBitmap(), true);
s.offset = marker.getHotspot();
s.billboard = marker.isBillboard();
mSymbolLayer.pushSymbol(s);
}
buckets.set(mSymbolLayer);
buckets.prepare();
compile();
}
protected void populate(int size) {
InternalItem[] tmp = new InternalItem[size];
for (int i = 0; i < size; i++) {
InternalItem it = new InternalItem();
tmp[i] = it;
it.item = mMarkerLayer.createItem(i);
/* pre-project points */
MercatorProjection.project(it.item.getPoint(), mMapPoint);
it.px = mMapPoint.x;
it.py = mMapPoint.y;
}
synchronized (this) {
mUpdate = true;
mItems = tmp;
}
}
public void update() {
mUpdate = true;
}
static TimSort<InternalItem> ZSORT = new TimSort<InternalItem>();
public static void sort(InternalItem[] a, int lo, int hi) {
int nRemaining = hi - lo;
if (nRemaining < 2) {
return;
}
ZSORT.doSort(a, zComparator, lo, hi);
}
final static Comparator<InternalItem> zComparator = new Comparator<InternalItem>() {
@Override
public int compare(InternalItem a, InternalItem b) {
if (a.visible && b.visible) {
if (a.dy > b.dy) {
return -1;
}
if (a.dy < b.dy) {
return 1;
}
} else if (a.visible) {
return -1;
} else if (b.visible) {
return 1;
}
return 0;
}
};
// /**
// * 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;
// }
// }
}