parent
84c45e153a
commit
4b30ca3242
@ -35,6 +35,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".BitmapTileMapActivity"
|
android:name=".BitmapTileMapActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize" />
|
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||||
|
<activity
|
||||||
|
android:name=".ClusterMarkerOverlayActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".JeoIndoorMapActivity"
|
android:name=".JeoIndoorMapActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize" />
|
android:configChanges="keyboardHidden|orientation|screenSize" />
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2017 devemux86
|
||||||
|
* Copyright 2017 nebular
|
||||||
|
*
|
||||||
|
* 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.android.test;
|
||||||
|
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.oscim.android.canvas.AndroidBitmap;
|
||||||
|
import org.oscim.core.GeoPoint;
|
||||||
|
import org.oscim.core.MapPosition;
|
||||||
|
import org.oscim.layers.marker.ClusterMarkerRenderer;
|
||||||
|
import org.oscim.layers.marker.ItemizedLayer;
|
||||||
|
import org.oscim.layers.marker.MarkerItem;
|
||||||
|
import org.oscim.layers.marker.MarkerSymbol;
|
||||||
|
import org.oscim.theme.VtmThemes;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class ClusterMarkerOverlayActivity extends BaseMapActivity {
|
||||||
|
|
||||||
|
private static final int COUNT = 5;
|
||||||
|
private static final float STEP = 100f / 110000f; // roughly 100 meters
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mMap.setTheme(VtmThemes.DEFAULT);
|
||||||
|
|
||||||
|
MapPosition pos = new MapPosition();
|
||||||
|
mMap.getMapPosition(pos);
|
||||||
|
pos.setZoomLevel(2);
|
||||||
|
mMap.setMapPosition(pos);
|
||||||
|
|
||||||
|
ItemizedLayer<MarkerItem> layer = new ItemizedLayer<>(
|
||||||
|
mMap,
|
||||||
|
ClusterMarkerRenderer.factory(null, new ClusterMarkerRenderer.ClusterStyle(0xffffffff, 0xff123456))
|
||||||
|
);
|
||||||
|
|
||||||
|
// add it top the map
|
||||||
|
mMap.layers().add(layer);
|
||||||
|
|
||||||
|
// create a symbol, for simplicity we will use this symbol for all created markers
|
||||||
|
MarkerSymbol symbol = new MarkerSymbol(
|
||||||
|
new AndroidBitmap(((BitmapDrawable) (getResources().getDrawable(R.drawable.marker_poi))).getBitmap()),
|
||||||
|
MarkerSymbol.HotspotPlace.CENTER
|
||||||
|
);
|
||||||
|
|
||||||
|
// create some markers spaced STEP degrees
|
||||||
|
GeoPoint center = pos.getGeoPoint();
|
||||||
|
ArrayList<MarkerItem> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int x = -COUNT; x < COUNT; x++) {
|
||||||
|
for (int y = -COUNT; y < COUNT; y++) {
|
||||||
|
double random = STEP * Math.random() * 2;
|
||||||
|
|
||||||
|
MarkerItem item = new MarkerItem(
|
||||||
|
"Demo Marker " + ((x * COUNT) + y),
|
||||||
|
"Your typical marker in your typical map",
|
||||||
|
new GeoPoint(center.getLatitude() + y * STEP + random, center.getLongitude() + x * STEP + random)
|
||||||
|
);
|
||||||
|
|
||||||
|
item.setMarker(symbol);
|
||||||
|
list.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add'em all at once
|
||||||
|
layer.addItems(list);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
* Copyright 2013 Hannes Janetzek
|
* Copyright 2013 Hannes Janetzek
|
||||||
* Copyright 2016-2017 devemux86
|
* Copyright 2016-2017 devemux86
|
||||||
* Copyright 2017 Longri
|
* Copyright 2017 Longri
|
||||||
|
* Copyright 2017 nebular
|
||||||
*
|
*
|
||||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||||
*
|
*
|
||||||
@ -54,6 +55,7 @@ public class Samples extends Activity {
|
|||||||
linearLayout.addView(createButton(RotateMarkerOverlayActivity.class));
|
linearLayout.addView(createButton(RotateMarkerOverlayActivity.class));
|
||||||
linearLayout.addView(createButton(AtlasMarkerOverlayActivity.class));
|
linearLayout.addView(createButton(AtlasMarkerOverlayActivity.class));
|
||||||
linearLayout.addView(createButton(AtlasMultiTextureActivity.class));
|
linearLayout.addView(createButton(AtlasMultiTextureActivity.class));
|
||||||
|
linearLayout.addView(createButton(ClusterMarkerOverlayActivity.class));
|
||||||
linearLayout.addView(createButton(PathOverlayActivity.class));
|
linearLayout.addView(createButton(PathOverlayActivity.class));
|
||||||
linearLayout.addView(createButton(LineTexActivity.class));
|
linearLayout.addView(createButton(LineTexActivity.class));
|
||||||
linearLayout.addView(createButton(VectorLayerMapActivity.class));
|
linearLayout.addView(createButton(VectorLayerMapActivity.class));
|
||||||
|
428
vtm/src/org/oscim/layers/marker/ClusterMarkerRenderer.java
Normal file
428
vtm/src/org/oscim/layers/marker/ClusterMarkerRenderer.java
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Hannes Janetzek
|
||||||
|
* Copyright 2016 Izumi Kawashima
|
||||||
|
* Copyright 2017 Longri
|
||||||
|
* Copyright 2017 devemux86
|
||||||
|
* Copyright 2017 nebular
|
||||||
|
*
|
||||||
|
* 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.backend.canvas.Bitmap;
|
||||||
|
import org.oscim.core.MercatorProjection;
|
||||||
|
import org.oscim.core.PointF;
|
||||||
|
import org.oscim.core.Tile;
|
||||||
|
import org.oscim.layers.marker.utils.ScreenUtils;
|
||||||
|
import org.oscim.layers.marker.utils.SparseIntArray;
|
||||||
|
import org.oscim.renderer.GLViewport;
|
||||||
|
import org.oscim.renderer.bucket.SymbolItem;
|
||||||
|
import org.oscim.utils.FastMath;
|
||||||
|
import org.oscim.utils.geom.GeometryUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension to the MarkerRenderer with item clustering support.
|
||||||
|
*/
|
||||||
|
public class ClusterMarkerRenderer extends MarkerRenderer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max number to display inside a cluster icon
|
||||||
|
*/
|
||||||
|
private static final int CLUSTER_MAXSIZE = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default color of number inside the icon. Would be super-cool to cook this into the map theme
|
||||||
|
*/
|
||||||
|
private static int CLUSTER_COLORTEXT = 0xff8000c0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default color of circle background
|
||||||
|
*/
|
||||||
|
private static final int CLUSTER_COLORBACK = 0xffffffff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Cluster Icon Size. This is the biggest size for clusters of CLUSTER_MAXSIZE elements. Smaller clusters will be slightly smaller
|
||||||
|
*/
|
||||||
|
private static final int MAP_MARKER_CLUSTER_SIZE_DP = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering grid square size, decrease to cluster more aggresively. Ideally this value is the typical marker size
|
||||||
|
*/
|
||||||
|
private static final int MAP_GRID_SIZE_DP = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cached bitmaps database, we will cache cluster bitmaps from 1 to MAX_SIZE
|
||||||
|
* and always use same bitmap for efficiency
|
||||||
|
*/
|
||||||
|
private static Bitmap[] mClusterBitmaps = new Bitmap[CLUSTER_MAXSIZE + 1];
|
||||||
|
|
||||||
|
private int mStyleBackground = CLUSTER_COLORBACK, mStyleForeground = CLUSTER_COLORTEXT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discrete scale step, used to trigger reclustering on significant scale change
|
||||||
|
*/
|
||||||
|
private int mScaleSaved = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use a flat Sparse array to calculate the clusters. The sparse array models a 2D map where every (x,y) denotes
|
||||||
|
* a grid slot, ie. 64x64dp. For efficiency I use a linear sparsearray with ARRindex = SLOTypos * max_x + SLOTxpos"
|
||||||
|
*/
|
||||||
|
private SparseIntArray mGridMap = new SparseIntArray(200); // initial space for 200 markers, that's not a lot of memory, and in most cases will avoid resizing the array
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable clustering or disable the functionality
|
||||||
|
*/
|
||||||
|
private boolean mClusteringEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a clustered marker renderer
|
||||||
|
*
|
||||||
|
* @param markerLayer The owner layer
|
||||||
|
* @param defaultSymbol The default symbol
|
||||||
|
* @param style The desired style, or NULL to disable clustering
|
||||||
|
*/
|
||||||
|
public ClusterMarkerRenderer(MarkerLayer<MarkerInterface> markerLayer, MarkerSymbol defaultSymbol, ClusterMarkerRenderer.ClusterStyle style) {
|
||||||
|
super(markerLayer, defaultSymbol);
|
||||||
|
|
||||||
|
mClusteringEnabled = style != null;
|
||||||
|
|
||||||
|
if (mClusteringEnabled) {
|
||||||
|
setClusterStyle(style.foreground, style.background);
|
||||||
|
for (int k = 0; k <= CLUSTER_MAXSIZE; k++) {
|
||||||
|
// cache bitmaps so render thread never creates them
|
||||||
|
// we create CLUSTER_MAXSIZE bitmaps. Bigger clusters will show like "+"
|
||||||
|
getClusterBitmap(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the cluster icon style. This is called by the constructor and cannot be made public because
|
||||||
|
* we pre-cache the icons at construction time so the renderer does not have to create them while rendering
|
||||||
|
*
|
||||||
|
* @param backgroundColor Background color
|
||||||
|
* @param foregroundColor text & border color
|
||||||
|
*/
|
||||||
|
private void setClusterStyle(int foregroundColor, int backgroundColor) {
|
||||||
|
mStyleBackground = backgroundColor;
|
||||||
|
mStyleForeground = foregroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void populate(int size) {
|
||||||
|
repopulateCluster(size, mScaleSaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repopulates item list clustering close markers. This is triggered from update() when
|
||||||
|
* a significant change in scale has happened.
|
||||||
|
*
|
||||||
|
* @param size Item list size
|
||||||
|
* @param scale current map scale
|
||||||
|
*/
|
||||||
|
private void repopulateCluster(int size, double scale) {
|
||||||
|
/* the grid slot size in px. increase to group more aggressively. currently set to marker size */
|
||||||
|
final int GRIDSIZE = ScreenUtils.getPixels(MAP_GRID_SIZE_DP);
|
||||||
|
|
||||||
|
/* the factor to map into Grid Coordinates (discrete squares of GRIDSIZE x GRIDSIZE) */
|
||||||
|
final double factor = (scale / GRIDSIZE);
|
||||||
|
|
||||||
|
InternalItem.Clustered[] tmp = new InternalItem.Clustered[size];
|
||||||
|
|
||||||
|
// clear grid map to count items that share the same "grid slot"
|
||||||
|
mGridMap.clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
InternalItem.Clustered it = tmp[i] = new InternalItem.Clustered();
|
||||||
|
|
||||||
|
it.item = mMarkerLayer.createItem(i);
|
||||||
|
|
||||||
|
/* pre-project points */
|
||||||
|
MercatorProjection.project(it.item.getPoint(), mMapPoint);
|
||||||
|
it.px = mMapPoint.x;
|
||||||
|
it.py = mMapPoint.y;
|
||||||
|
|
||||||
|
// items can be declared non-clusterable
|
||||||
|
if (!(it.item instanceof MarkerItem.NonClusterable)) {
|
||||||
|
|
||||||
|
final int
|
||||||
|
absposx = (int) (it.px * factor), // absolute item X position in the grid
|
||||||
|
absposy = (int) (it.py * factor), // absolute item Y position
|
||||||
|
maxcols = (int) factor, // Grid number of columns
|
||||||
|
itemGridIndex = absposx + absposy * maxcols; // Index in the sparsearray map
|
||||||
|
|
||||||
|
// we store in the linear sparsearray the index of the marker,
|
||||||
|
// ie, index = y * maxcols + x; array[index} = markerIndex
|
||||||
|
|
||||||
|
// Lets check if there's already an item in the grid slot
|
||||||
|
final int storedIndexInGridSlot = mGridMap.get(itemGridIndex, -1);
|
||||||
|
|
||||||
|
if (storedIndexInGridSlot == -1) {
|
||||||
|
// no item at that grid position. The grid slot is free so let's
|
||||||
|
// store this item "i" (we identify every item by its InternalItem index)
|
||||||
|
|
||||||
|
mGridMap.put(itemGridIndex, i);
|
||||||
|
//Log.v(TAG, "UNclustered item at " + itemGridIndex);
|
||||||
|
} else {
|
||||||
|
// at that grid position there's already a marker index
|
||||||
|
// mark this item as clustered out, so it will be skipped in the update() call
|
||||||
|
|
||||||
|
it.clusteredOut = true;
|
||||||
|
|
||||||
|
// and increment the count on its "parent" that will from now on act as a cluster
|
||||||
|
tmp[storedIndexInGridSlot].clusterSize++;
|
||||||
|
|
||||||
|
//Log.v(TAG, "Clustered item at " + itemGridIndex + ", \'parent\' size " + (tmp[storedIndexInGridSlot].clusterSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All ready for update. */
|
||||||
|
synchronized (this) {
|
||||||
|
mUpdate = true;
|
||||||
|
mItems = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void update(GLViewport v) {
|
||||||
|
final double scale = Tile.SIZE * v.pos.scale;
|
||||||
|
|
||||||
|
if (mClusteringEnabled) {
|
||||||
|
/*
|
||||||
|
Clustering check: If clustering is enabled and there's been a significant scale change
|
||||||
|
trigger repopulation and return. After repopulation, this will be called again
|
||||||
|
*/
|
||||||
|
|
||||||
|
// (int) log of scale gives us adequate steps to trigger clustering
|
||||||
|
int scalepow = FastMath.log2((int) scale);
|
||||||
|
|
||||||
|
if (scalepow != mScaleSaved) {
|
||||||
|
mScaleSaved = scalepow;
|
||||||
|
|
||||||
|
// post repopulation to the main thread
|
||||||
|
mMarkerLayer.map().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
repopulateCluster(mItems.length, scale);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// and get out of here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v.changed() && !mUpdate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mUpdate = false;
|
||||||
|
|
||||||
|
double mx = v.pos.x;
|
||||||
|
double my = v.pos.y;
|
||||||
|
|
||||||
|
//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 itm : mItems) {
|
||||||
|
InternalItem.Clustered it = (InternalItem.Clustered) itm;
|
||||||
|
|
||||||
|
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 ((it.clusteredOut) || (!GeometryUtils.pointInPoly(it.x, it.y, mBox, 8, 0))) {
|
||||||
|
// either properly invisible, or clustered out. Items marked as clustered out mean there's another item
|
||||||
|
// on the same-ish position that will be promoted to cluster marker, so this particular item is considered
|
||||||
|
// invisible
|
||||||
|
|
||||||
|
if (it.visible && (!it.clusteredOut)) {
|
||||||
|
// it was previously visible, but now it won't
|
||||||
|
it.changes = true;
|
||||||
|
// changes to invible
|
||||||
|
//changesInvisible++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// item IS definitely visible
|
||||||
|
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;
|
||||||
|
|
||||||
|
// why do we sort ? z-index?
|
||||||
|
sort(mItems, 0, mItems.length);
|
||||||
|
//log.debug(Arrays.toString(mItems));
|
||||||
|
|
||||||
|
for (InternalItem itm : mItems) {
|
||||||
|
InternalItem.Clustered it = (InternalItem.Clustered) itm;
|
||||||
|
|
||||||
|
// skip invisible AND clustered-out
|
||||||
|
if ((!it.visible) || (it.clusteredOut))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (it.changes) {
|
||||||
|
it.visible = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolItem s = SymbolItem.pool.get();
|
||||||
|
|
||||||
|
if (it.clusterSize > 0) {
|
||||||
|
// this item will act as a cluster, just use a proper bitmap
|
||||||
|
// depending on cluster size, instead of its marker
|
||||||
|
|
||||||
|
Bitmap bitmap = getClusterBitmap(it.clusterSize + 1);
|
||||||
|
s.set(it.x, it.y, bitmap, true);
|
||||||
|
s.offset = new PointF(0.5f, 0.5f);
|
||||||
|
s.billboard = true; // could be a parameter
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// normal item, use its marker
|
||||||
|
|
||||||
|
MarkerSymbol symbol = it.item.getMarker();
|
||||||
|
|
||||||
|
if (symbol == null)
|
||||||
|
symbol = mDefaultMarker;
|
||||||
|
|
||||||
|
s.set(it.x, it.y, symbol.getBitmap(), true);
|
||||||
|
s.offset = symbol.getHotspot();
|
||||||
|
s.billboard = symbol.isBillboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
mSymbolLayer.pushSymbol(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets.set(mSymbolLayer);
|
||||||
|
buckets.prepare();
|
||||||
|
|
||||||
|
compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a bitmap for a given cluster size
|
||||||
|
*
|
||||||
|
* @param size The cluster size. Can be greater than CLUSTER_MAXSIZE.
|
||||||
|
* @return A somewhat cool bitmap to be used as the cluster marker
|
||||||
|
*/
|
||||||
|
private Bitmap getClusterBitmap(int size) {
|
||||||
|
final String strValue;
|
||||||
|
|
||||||
|
if (size >= CLUSTER_MAXSIZE) {
|
||||||
|
// restrict cluster indicator size. Bigger clusters will show as "+" instead of ie. "45"
|
||||||
|
size = CLUSTER_MAXSIZE;
|
||||||
|
strValue = "+";
|
||||||
|
} else {
|
||||||
|
strValue = String.valueOf(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return cached bitmap if exists. cache hit !
|
||||||
|
if (mClusterBitmaps[size] != null)
|
||||||
|
return mClusterBitmaps[size];
|
||||||
|
|
||||||
|
// create and cache bitmap. This is unacceptable inside the GL thread,
|
||||||
|
// so we'll call this routine at the beginning to pre-cache all bitmaps
|
||||||
|
|
||||||
|
ScreenUtils.ClusterDrawable drawable = new ScreenUtils.ClusterDrawable(
|
||||||
|
MAP_MARKER_CLUSTER_SIZE_DP - CLUSTER_MAXSIZE + size, // make size dependent on cluster size
|
||||||
|
mStyleForeground,
|
||||||
|
mStyleBackground,
|
||||||
|
strValue
|
||||||
|
);
|
||||||
|
|
||||||
|
mClusterBitmaps[size] = drawable.getBitmap();
|
||||||
|
return mClusterBitmaps[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to wrap the cluster icon style properties
|
||||||
|
*/
|
||||||
|
public static class ClusterStyle {
|
||||||
|
final int background;
|
||||||
|
final int foreground;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Cluster style
|
||||||
|
*
|
||||||
|
* @param fore Foreground (border and text) color
|
||||||
|
* @param back Background (circle) color
|
||||||
|
*/
|
||||||
|
|
||||||
|
public ClusterStyle(int fore, int back) {
|
||||||
|
foreground = fore;
|
||||||
|
background = back;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for instantiating this renderer via a factory, so the layer construction semantic is more pleasing to the eye
|
||||||
|
*
|
||||||
|
* @param defaultSymbol Default symbol to use if the Marker is not assigned a symbol
|
||||||
|
* @param style Cluster icon style, or NULL to disable clustering functionality
|
||||||
|
* @return A factory to be passed to the ItemizedLayer constructor in order to enable the cluster functionality
|
||||||
|
*/
|
||||||
|
public static MarkerRendererFactory factory(final MarkerSymbol defaultSymbol, final ClusterStyle style) {
|
||||||
|
return new MarkerRendererFactory() {
|
||||||
|
@Override
|
||||||
|
public MarkerRenderer create(MarkerLayer markerLayer) {
|
||||||
|
return new ClusterMarkerRenderer(markerLayer, defaultSymbol, style);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
57
vtm/src/org/oscim/layers/marker/InternalItem.java
Normal file
57
vtm/src/org/oscim/layers/marker/InternalItem.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Hannes Janetzek
|
||||||
|
* Copyright 2016 Izumi Kawashima
|
||||||
|
* Copyright 2017 Longri
|
||||||
|
* Copyright 2017 devemux86
|
||||||
|
* Copyright 2017 nebular
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal representation of a marker.
|
||||||
|
*/
|
||||||
|
class InternalItem {
|
||||||
|
|
||||||
|
MarkerInterface item;
|
||||||
|
boolean visible;
|
||||||
|
boolean changes;
|
||||||
|
float x, y;
|
||||||
|
double px, py;
|
||||||
|
float dy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "\n" + x + ":" + y + " / " + dy + " " + visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension to the above class for clustered items. This could be a separate 1st level class,
|
||||||
|
* but it is included here not to pollute the source tree with tiny new files.
|
||||||
|
* It only adds a couple properties to InternalItem, and the semantics "InternalItem.Clustered"
|
||||||
|
* are not bad.
|
||||||
|
*/
|
||||||
|
static class Clustered extends InternalItem {
|
||||||
|
/**
|
||||||
|
* If this is >0, this item will be displayed as a cluster circle, with size clusterSize+1.
|
||||||
|
*/
|
||||||
|
int clusterSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is true, this item is hidden (because it's represented by another InternalItem acting as cluster.
|
||||||
|
*/
|
||||||
|
boolean clusteredOut;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
* Copyright 2016 devemux86
|
* Copyright 2016 devemux86
|
||||||
* Copyright 2016 Erik Duisters
|
* Copyright 2016 Erik Duisters
|
||||||
* Copyright 2017 Longri
|
* Copyright 2017 Longri
|
||||||
|
* Copyright 2017 nebular
|
||||||
*
|
*
|
||||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||||
*
|
*
|
||||||
@ -81,4 +82,18 @@ public class MarkerItem implements MarkerInterface {
|
|||||||
if (mMarker != null)
|
if (mMarker != null)
|
||||||
mMarker.setRotation(rotation);
|
mMarker.setRotation(rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a MarkerItem is created using this convenience class instead of MarkerItem,
|
||||||
|
* this specific item will not be clusterable.
|
||||||
|
*/
|
||||||
|
public static class NonClusterable extends MarkerItem {
|
||||||
|
public NonClusterable(String title, String description, GeoPoint geoPoint) {
|
||||||
|
super(null, title, description, geoPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonClusterable(Object uid, String title, String description, GeoPoint geoPoint) {
|
||||||
|
super(uid, title, description, geoPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
* Copyright 2016 Izumi Kawashima
|
* Copyright 2016 Izumi Kawashima
|
||||||
* Copyright 2017 Longri
|
* Copyright 2017 Longri
|
||||||
* Copyright 2017 devemux86
|
* Copyright 2017 devemux86
|
||||||
|
* Copyright 2017 nebular
|
||||||
*
|
*
|
||||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||||
*
|
*
|
||||||
@ -17,7 +18,6 @@
|
|||||||
* 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;
|
||||||
|
|
||||||
import org.oscim.core.MercatorProjection;
|
import org.oscim.core.MercatorProjection;
|
||||||
@ -36,10 +36,10 @@ public class MarkerRenderer extends BucketRenderer {
|
|||||||
|
|
||||||
protected final MarkerSymbol mDefaultMarker;
|
protected final MarkerSymbol mDefaultMarker;
|
||||||
|
|
||||||
private final SymbolBucket mSymbolLayer;
|
protected final SymbolBucket mSymbolLayer;
|
||||||
private final float[] mBox = new float[8];
|
protected final float[] mBox = new float[8];
|
||||||
private final MarkerLayer<MarkerInterface> mMarkerLayer;
|
protected final MarkerLayer<MarkerInterface> mMarkerLayer;
|
||||||
private final Point mMapPoint = new Point();
|
protected final Point mMapPoint = new Point();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* increase view to show items that are partially visible
|
* increase view to show items that are partially visible
|
||||||
@ -49,23 +49,9 @@ public class MarkerRenderer extends BucketRenderer {
|
|||||||
/**
|
/**
|
||||||
* flag to force update of markers
|
* flag to force update of markers
|
||||||
*/
|
*/
|
||||||
private boolean mUpdate;
|
protected boolean mUpdate;
|
||||||
|
|
||||||
private InternalItem[] mItems;
|
protected InternalItem[] mItems;
|
||||||
|
|
||||||
static class InternalItem {
|
|
||||||
MarkerInterface 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<MarkerInterface> markerLayer, MarkerSymbol defaultSymbol) {
|
public MarkerRenderer(MarkerLayer<MarkerInterface> markerLayer, MarkerSymbol defaultSymbol) {
|
||||||
mSymbolLayer = new SymbolBucket();
|
mSymbolLayer = new SymbolBucket();
|
||||||
|
89
vtm/src/org/oscim/layers/marker/utils/GrowingArrayUtils.java
Normal file
89
vtm/src/org/oscim/layers/marker/utils/GrowingArrayUtils.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 nebular
|
||||||
|
*
|
||||||
|
* 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.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This comes for Android API. It is used by the INT SPARSEARRAY.
|
||||||
|
* The Android version uses some native routines to create the arrays, I suppose for performance.
|
||||||
|
* However our use of this class is very basic, and we will avoid resizing the arrays as much
|
||||||
|
* as possible.
|
||||||
|
* <p>
|
||||||
|
* A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
|
||||||
|
* arrays. Common array operations are implemented for efficient use in dynamic containers.
|
||||||
|
* <p>
|
||||||
|
* All methods in this class assume that the length of an array is equivalent to its capacity and
|
||||||
|
* NOT the number of elements in the array. The current size of the array is always passed in as a
|
||||||
|
* parameter.
|
||||||
|
*/
|
||||||
|
public final class GrowingArrayUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an element to the end of the array, growing the array if there is no more room.
|
||||||
|
*
|
||||||
|
* @param array The array to which to append the element. This must NOT be null.
|
||||||
|
* @param currentSize The number of elements in the array. Must be less than or equal to
|
||||||
|
* array.length.
|
||||||
|
* @param element The element to append.
|
||||||
|
* @return the array to which the element was appended. This may be different than the given
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
public static int[] append(int[] array, int currentSize, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
if (currentSize + 1 > array.length) {
|
||||||
|
int[] newArray = new int[growSize(currentSize)];
|
||||||
|
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
array[currentSize] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts an element into the array at the specified index, growing the array if there is no
|
||||||
|
* more room.
|
||||||
|
*
|
||||||
|
* @param array The array to which to append the element. Must NOT be null.
|
||||||
|
* @param currentSize The number of elements in the array. Must be less than or equal to
|
||||||
|
* array.length.
|
||||||
|
* @param element The element to insert.
|
||||||
|
* @return the array to which the element was appended. This may be different than the given
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
public static int[] insert(int[] array, int currentSize, int index, int element) {
|
||||||
|
assert currentSize <= array.length;
|
||||||
|
if (currentSize + 1 <= array.length) {
|
||||||
|
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||||
|
array[index] = element;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
int[] newArray = new int[growSize(currentSize)];
|
||||||
|
System.arraycopy(array, 0, newArray, 0, index);
|
||||||
|
newArray[index] = element;
|
||||||
|
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the current size of an array, returns an ideal size to which the array should grow.
|
||||||
|
* This is typically double the given size, but should not be relied upon to do so in the
|
||||||
|
* future.
|
||||||
|
*/
|
||||||
|
public static int growSize(int currentSize) {
|
||||||
|
return currentSize <= 4 ? 8 : currentSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GrowingArrayUtils() {
|
||||||
|
}
|
||||||
|
}
|
106
vtm/src/org/oscim/layers/marker/utils/ScreenUtils.java
Normal file
106
vtm/src/org/oscim/layers/marker/utils/ScreenUtils.java
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 nebular
|
||||||
|
*
|
||||||
|
* 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.utils;
|
||||||
|
|
||||||
|
import org.oscim.backend.CanvasAdapter;
|
||||||
|
import org.oscim.backend.canvas.Bitmap;
|
||||||
|
import org.oscim.backend.canvas.Canvas;
|
||||||
|
import org.oscim.backend.canvas.Paint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple utility class to make clustered markers functionality self-contained.
|
||||||
|
* Includes a method to translate between DPs and PXs and a circular icon generator.
|
||||||
|
*/
|
||||||
|
public class ScreenUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.android.com/reference/android/util/DisplayMetrics.html#DENSITY_DEFAULT
|
||||||
|
*/
|
||||||
|
private static final float REFERENCE_DPI = 160;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pixels from DPs
|
||||||
|
*
|
||||||
|
* @param dp Value in DPs
|
||||||
|
* @return Value in PX according to screen density
|
||||||
|
*/
|
||||||
|
public static int getPixels(float dp) {
|
||||||
|
return (int) (CanvasAdapter.dpi / REFERENCE_DPI * dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClusterDrawable {
|
||||||
|
private Paint mPaintText = CanvasAdapter.newPaint();
|
||||||
|
private Paint mPaintCircle = CanvasAdapter.newPaint(), mPaintBorder = CanvasAdapter.newPaint();
|
||||||
|
private int mSize;
|
||||||
|
private String mText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a circle with a number inside
|
||||||
|
*
|
||||||
|
* @param sizedp Size in DPs
|
||||||
|
* @param foregroundColor Foreground
|
||||||
|
* @param backgroundColor Background
|
||||||
|
* @param text Text inside. Will only work for a single character!
|
||||||
|
*/
|
||||||
|
public ClusterDrawable(int sizedp, int foregroundColor, int backgroundColor, String text) {
|
||||||
|
setup(sizedp, foregroundColor, backgroundColor);
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup(int sizedp, int foregroundColor, int backgroundColor) {
|
||||||
|
mSize = ScreenUtils.getPixels(sizedp);
|
||||||
|
mPaintText.setTextSize(ScreenUtils.getPixels((int) (sizedp * 0.6666666)));
|
||||||
|
mPaintText.setColor(foregroundColor);
|
||||||
|
|
||||||
|
// NOT SUPPORTED on current backends (Feb 2017)
|
||||||
|
// mPaintText.setTextAlign(Paint.Align.CENTER);
|
||||||
|
|
||||||
|
mPaintCircle.setColor(backgroundColor);
|
||||||
|
mPaintCircle.setStyle(Paint.Style.FILL);
|
||||||
|
|
||||||
|
mPaintBorder.setColor(foregroundColor);
|
||||||
|
mPaintBorder.setStyle(Paint.Style.STROKE);
|
||||||
|
mPaintBorder.setStrokeWidth(2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setText(String text) {
|
||||||
|
mText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw(Canvas canvas) {
|
||||||
|
int halfsize = mSize >> 1;
|
||||||
|
|
||||||
|
// outline
|
||||||
|
canvas.drawCircle(halfsize, halfsize, halfsize, mPaintCircle);
|
||||||
|
// fill
|
||||||
|
canvas.drawCircle(halfsize, halfsize, halfsize, mPaintBorder);
|
||||||
|
// draw the number, the centering is not perfect without a measureText or alignment
|
||||||
|
canvas.drawText(mText, halfsize * 0.6f, halfsize + (halfsize >> 1), mPaintText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getBitmap() {
|
||||||
|
int width = mSize, height = mSize;
|
||||||
|
width = width > 0 ? width : 1;
|
||||||
|
height = height > 0 ? height : 1;
|
||||||
|
|
||||||
|
Bitmap bitmap = CanvasAdapter.newBitmap(width, height, 0);
|
||||||
|
Canvas canvas = CanvasAdapter.newCanvas();
|
||||||
|
canvas.setBitmap(bitmap);
|
||||||
|
draw(canvas);
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
264
vtm/src/org/oscim/layers/marker/utils/SparseIntArray.java
Normal file
264
vtm/src/org/oscim/layers/marker/utils/SparseIntArray.java
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.oscim.layers.marker.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cloned from Android API: SparseInt Array
|
||||||
|
*/
|
||||||
|
public class SparseIntArray implements Cloneable {
|
||||||
|
private int[] mKeys;
|
||||||
|
private int[] mValues;
|
||||||
|
private int mSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SparseIntArray containing no mappings that will not
|
||||||
|
* require any additional memory allocation to store the specified
|
||||||
|
* number of mappings. If you supply an initial capacity of 0, the
|
||||||
|
* sparse array will be initialized with a light-weight representation
|
||||||
|
* not requiring any additional array allocations.
|
||||||
|
*/
|
||||||
|
public SparseIntArray(int initialCapacity) {
|
||||||
|
if (initialCapacity == 0)
|
||||||
|
initialCapacity = 100;
|
||||||
|
|
||||||
|
/** Android original code used native functions to create this array */
|
||||||
|
mKeys = new int[initialCapacity];
|
||||||
|
mValues = new int[mKeys.length];
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SparseIntArray clone() {
|
||||||
|
SparseIntArray clone = null;
|
||||||
|
try {
|
||||||
|
clone = (SparseIntArray) super.clone();
|
||||||
|
clone.mKeys = mKeys.clone();
|
||||||
|
clone.mValues = mValues.clone();
|
||||||
|
} catch (CloneNotSupportedException cnse) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the int mapped from the specified key, or <code>0</code>
|
||||||
|
* if no such mapping has been made.
|
||||||
|
*/
|
||||||
|
public int get(int key) {
|
||||||
|
return get(key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the int mapped from the specified key, or the specified value
|
||||||
|
* if no such mapping has been made.
|
||||||
|
*/
|
||||||
|
public int get(int key, int valueIfKeyNotFound) {
|
||||||
|
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||||
|
if (i < 0) {
|
||||||
|
return valueIfKeyNotFound;
|
||||||
|
} else {
|
||||||
|
return mValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the mapping from the specified key, if there was any.
|
||||||
|
*/
|
||||||
|
public void delete(int key) {
|
||||||
|
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||||
|
if (i >= 0) {
|
||||||
|
removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the mapping at the given index.
|
||||||
|
*/
|
||||||
|
public void removeAt(int index) {
|
||||||
|
System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
|
||||||
|
System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
|
||||||
|
mSize--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a mapping from the specified key to the specified value,
|
||||||
|
* replacing the previous mapping from the specified key if there
|
||||||
|
* was one.
|
||||||
|
*/
|
||||||
|
public void put(int key, int value) {
|
||||||
|
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||||
|
if (i >= 0) {
|
||||||
|
mValues[i] = value;
|
||||||
|
} else {
|
||||||
|
i = ~i;
|
||||||
|
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
|
||||||
|
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
|
||||||
|
mSize++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of key-value mappings that this SparseIntArray
|
||||||
|
* currently stores.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an index in the range <code>0...size()-1</code>, returns
|
||||||
|
* the key from the <code>index</code>th key-value mapping that this
|
||||||
|
* SparseIntArray stores.
|
||||||
|
* <p>
|
||||||
|
* <p>The keys corresponding to indices in ascending order are guaranteed to
|
||||||
|
* be in ascending order, e.g., <code>keyAt(0)</code> will return the
|
||||||
|
* smallest key and <code>keyAt(size()-1)</code> will return the largest
|
||||||
|
* key.</p>
|
||||||
|
*/
|
||||||
|
public int keyAt(int index) {
|
||||||
|
return mKeys[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an index in the range <code>0...size()-1</code>, returns
|
||||||
|
* the value from the <code>index</code>th key-value mapping that this
|
||||||
|
* SparseIntArray stores.
|
||||||
|
* <p>
|
||||||
|
* <p>The values corresponding to indices in ascending order are guaranteed
|
||||||
|
* to be associated with keys in ascending order, e.g.,
|
||||||
|
* <code>valueAt(0)</code> will return the value associated with the
|
||||||
|
* smallest key and <code>valueAt(size()-1)</code> will return the value
|
||||||
|
* associated with the largest key.</p>
|
||||||
|
*/
|
||||||
|
public int valueAt(int index) {
|
||||||
|
return mValues[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly set the value at a particular index.
|
||||||
|
*/
|
||||||
|
public void setValueAt(int index, int value) {
|
||||||
|
mValues[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index for which {@link #keyAt} would return the
|
||||||
|
* specified key, or a negative number if the specified
|
||||||
|
* key is not mapped.
|
||||||
|
*/
|
||||||
|
public int indexOfKey(int key) {
|
||||||
|
return ContainerHelpers.binarySearch(mKeys, mSize, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an index for which {@link #valueAt} would return the
|
||||||
|
* specified key, or a negative number if no keys map to the
|
||||||
|
* specified value.
|
||||||
|
* Beware that this is a linear search, unlike lookups by key,
|
||||||
|
* and that multiple keys can map to the same value and this will
|
||||||
|
* find only one of them.
|
||||||
|
*/
|
||||||
|
public int indexOfValue(int value) {
|
||||||
|
for (int i = 0; i < mSize; i++)
|
||||||
|
if (mValues[i] == value)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all key-value mappings from this SparseIntArray.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a key/value pair into the array, optimizing for the case where
|
||||||
|
* the key is greater than all existing keys in the array.
|
||||||
|
*/
|
||||||
|
public void append(int key, int value) {
|
||||||
|
if (mSize != 0 && key <= mKeys[mSize - 1]) {
|
||||||
|
put(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
|
||||||
|
mValues = GrowingArrayUtils.append(mValues, mSize, value);
|
||||||
|
mSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>
|
||||||
|
* <p>This implementation composes a string by iterating over its mappings.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (size() <= 0) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
StringBuilder buffer = new StringBuilder(mSize * 28);
|
||||||
|
buffer.append('{');
|
||||||
|
for (int i = 0; i < mSize; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
buffer.append(", ");
|
||||||
|
}
|
||||||
|
int key = keyAt(i);
|
||||||
|
buffer.append(key);
|
||||||
|
buffer.append('=');
|
||||||
|
int value = valueAt(i);
|
||||||
|
buffer.append(value);
|
||||||
|
}
|
||||||
|
buffer.append('}');
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ContainerHelpers {
|
||||||
|
// This is Arrays.binarySearch(), but doesn't do any argument validation.
|
||||||
|
static int binarySearch(int[] array, int size, int value) {
|
||||||
|
int lo = 0;
|
||||||
|
int hi = size - 1;
|
||||||
|
while (lo <= hi) {
|
||||||
|
final int mid = (lo + hi) >>> 1;
|
||||||
|
final int midVal = array[mid];
|
||||||
|
if (midVal < value) {
|
||||||
|
lo = mid + 1;
|
||||||
|
} else if (midVal > value) {
|
||||||
|
hi = mid - 1;
|
||||||
|
} else {
|
||||||
|
return mid; // value found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ~lo; // value not present
|
||||||
|
}
|
||||||
|
|
||||||
|
static int binarySearch(long[] array, int size, long value) {
|
||||||
|
int lo = 0;
|
||||||
|
int hi = size - 1;
|
||||||
|
while (lo <= hi) {
|
||||||
|
final int mid = (lo + hi) >>> 1;
|
||||||
|
final long midVal = array[mid];
|
||||||
|
if (midVal < value) {
|
||||||
|
lo = mid + 1;
|
||||||
|
} else if (midVal > value) {
|
||||||
|
hi = mid - 1;
|
||||||
|
} else {
|
||||||
|
return mid; // value found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ~lo; // value not present
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user