kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat(map): implement marker clustering (#1287)
rodzic
f689d772d6
commit
48365218e2
|
@ -93,8 +93,10 @@ class MarkerWithLabel(mapView: MapView?, label: String, emoji: String? = null) :
|
||||||
val bgRect = getTextBackgroundSize(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET))
|
val bgRect = getTextBackgroundSize(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET))
|
||||||
bgRect.inset(-8F, -2F)
|
bgRect.inset(-8F, -2F)
|
||||||
|
|
||||||
c.drawRoundRect(bgRect, LABEL_CORNER_RADIUS, LABEL_CORNER_RADIUS, bgPaint)
|
if(mLabel.isNotEmpty()) {
|
||||||
c.drawText(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET), textPaint)
|
c.drawRoundRect(bgRect, LABEL_CORNER_RADIUS, LABEL_CORNER_RADIUS, bgPaint)
|
||||||
|
c.drawText(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET), textPaint)
|
||||||
|
}
|
||||||
mEmoji?.let { c.drawText(it, (p.x - 0f), (p.y - 30f), emojiPaint) }
|
mEmoji?.let { c.drawText(it, (p.x - 0f), (p.y - 30f), emojiPaint) }
|
||||||
|
|
||||||
getPrecisionMeters()?.let { radius ->
|
getPrecisionMeters()?.let { radius ->
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
package com.geeksville.mesh.model.map.clustering;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import org.osmdroid.api.IGeoPoint;
|
||||||
|
import org.osmdroid.bonuspack.kml.KmlFeature;
|
||||||
|
import org.osmdroid.util.BoundingBox;
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import org.osmdroid.views.MapView;
|
||||||
|
import org.osmdroid.views.overlay.Overlay;
|
||||||
|
import com.geeksville.mesh.model.map.MarkerWithLabel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An overlay allowing to perform markers clustering.
|
||||||
|
* Usage: put your markers inside with add(Marker), and add the MarkerClusterer to the map overlays.
|
||||||
|
* Depending on the zoom level, markers will be displayed separately, or grouped as a single Marker. <br/>
|
||||||
|
*
|
||||||
|
* This abstract class provides the framework. Sub-classes have to implement the clustering algorithm,
|
||||||
|
* and the rendering of a cluster.
|
||||||
|
*
|
||||||
|
* @author M.Kergall
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class MarkerClusterer extends Overlay {
|
||||||
|
|
||||||
|
/** impossible value for zoom level, to force clustering */
|
||||||
|
protected static final int FORCE_CLUSTERING = -1;
|
||||||
|
|
||||||
|
protected ArrayList<MarkerWithLabel> mItems = new ArrayList<MarkerWithLabel>();
|
||||||
|
protected Point mPoint = new Point();
|
||||||
|
protected ArrayList<StaticCluster> mClusters = new ArrayList<StaticCluster>();
|
||||||
|
protected int mLastZoomLevel;
|
||||||
|
protected Bitmap mClusterIcon;
|
||||||
|
protected String mName, mDescription;
|
||||||
|
|
||||||
|
// abstract methods:
|
||||||
|
|
||||||
|
/** clustering algorithm */
|
||||||
|
public abstract ArrayList<StaticCluster> clusterer(MapView mapView);
|
||||||
|
/** Build the marker for a cluster. */
|
||||||
|
public abstract MarkerWithLabel buildClusterMarker(StaticCluster cluster, MapView mapView);
|
||||||
|
/** build clusters markers to be used at next draw */
|
||||||
|
public abstract void renderer(ArrayList<StaticCluster> clusters, Canvas canvas, MapView mapView);
|
||||||
|
|
||||||
|
public MarkerClusterer() {
|
||||||
|
super();
|
||||||
|
mLastZoomLevel = FORCE_CLUSTERING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name){
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName(){
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description){
|
||||||
|
mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription(){
|
||||||
|
return mDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the cluster icon to be drawn when a cluster contains more than 1 marker.
|
||||||
|
* If not set, default will be the default osmdroid marker icon (which is really inappropriate as a cluster icon). */
|
||||||
|
public void setIcon(Bitmap icon){
|
||||||
|
mClusterIcon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add the Marker.
|
||||||
|
* Important: Markers added in a MarkerClusterer should not be added in the map overlays. */
|
||||||
|
public void add(MarkerWithLabel marker){
|
||||||
|
mItems.add(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Force a rebuild of clusters at next draw, even without a zooming action.
|
||||||
|
* Should be done when you changed the content of a MarkerClusterer. */
|
||||||
|
public void invalidate(){
|
||||||
|
mLastZoomLevel = FORCE_CLUSTERING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the Marker at id (starting at 0) */
|
||||||
|
public MarkerWithLabel getItem(int id){
|
||||||
|
return mItems.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the list of Markers. */
|
||||||
|
public ArrayList<MarkerWithLabel> getItems(){
|
||||||
|
return mItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void hideInfoWindows(){
|
||||||
|
for (MarkerWithLabel m : mItems){
|
||||||
|
if (m.isInfoWindowShown())
|
||||||
|
m.closeInfoWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
||||||
|
if (shadow)
|
||||||
|
return;
|
||||||
|
//if zoom has changed and mapView is now stable, rebuild clusters:
|
||||||
|
int zoomLevel = mapView.getZoomLevel();
|
||||||
|
if (zoomLevel != mLastZoomLevel && !mapView.isAnimating()){
|
||||||
|
hideInfoWindows();
|
||||||
|
mClusters = clusterer(mapView);
|
||||||
|
renderer(mClusters, canvas, mapView);
|
||||||
|
mLastZoomLevel = zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (StaticCluster cluster:mClusters){
|
||||||
|
MarkerWithLabel marker = cluster.getMarker();
|
||||||
|
marker.draw(canvas, mapView, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<StaticCluster> reversedClusters() {
|
||||||
|
return new Iterable<StaticCluster>() {
|
||||||
|
@Override
|
||||||
|
public Iterator<StaticCluster> iterator() {
|
||||||
|
final ListIterator<StaticCluster> i = mClusters.listIterator(mClusters.size());
|
||||||
|
return new Iterator<StaticCluster>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return i.hasPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StaticCluster next() {
|
||||||
|
return i.previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onSingleTapConfirmed(final MotionEvent event, final MapView mapView){
|
||||||
|
for (final StaticCluster cluster : reversedClusters()) {
|
||||||
|
if (cluster.getMarker().onSingleTapConfirmed(event, mapView))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onLongPress(final MotionEvent event, final MapView mapView) {
|
||||||
|
for (final StaticCluster cluster : reversedClusters()) {
|
||||||
|
if (cluster.getMarker().onLongPress(event, mapView))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onTouchEvent(final MotionEvent event, final MapView mapView) {
|
||||||
|
for (StaticCluster cluster : reversedClusters()) {
|
||||||
|
if (cluster.getMarker().onTouchEvent(event, mapView))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onDoubleTap(final MotionEvent event, final MapView mapView) {
|
||||||
|
for (final StaticCluster cluster : reversedClusters()) {
|
||||||
|
if (cluster.getMarker().onDoubleTap(event, mapView))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public BoundingBox getBounds(){
|
||||||
|
if (mItems.size() == 0)
|
||||||
|
return null;
|
||||||
|
double minLat = Double.MAX_VALUE;
|
||||||
|
double minLon = Double.MAX_VALUE;
|
||||||
|
double maxLat = -Double.MAX_VALUE;
|
||||||
|
double maxLon = -Double.MAX_VALUE;
|
||||||
|
for (final MarkerWithLabel item : mItems) {
|
||||||
|
final double latitude = item.getPosition().getLatitude();
|
||||||
|
final double longitude = item.getPosition().getLongitude();
|
||||||
|
minLat = Math.min(minLat, latitude);
|
||||||
|
minLon = Math.min(minLon, longitude);
|
||||||
|
maxLat = Math.max(maxLat, latitude);
|
||||||
|
maxLon = Math.max(maxLon, longitude);
|
||||||
|
}
|
||||||
|
return new BoundingBox(maxLat, maxLon, minLat, minLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.geeksville.mesh.model.map.clustering;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import org.osmdroid.bonuspack.R;
|
||||||
|
import org.osmdroid.util.BoundingBox;
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import org.osmdroid.views.MapView;
|
||||||
|
import com.geeksville.mesh.model.map.MarkerWithLabel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radius-based Clustering algorithm:
|
||||||
|
* create a cluster using the first point from the cloned list.
|
||||||
|
* All points that are found within the neighborhood are added to this cluster.
|
||||||
|
* Then all the neighbors and the main point are removed from the list of points.
|
||||||
|
* It continues until the list is empty.
|
||||||
|
*
|
||||||
|
* Largely inspired from GridMarkerClusterer by M.Kergall
|
||||||
|
*
|
||||||
|
* @author sidorovroman92@gmail.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RadiusMarkerClusterer extends MarkerClusterer {
|
||||||
|
|
||||||
|
protected int mMaxClusteringZoomLevel = 17;
|
||||||
|
protected int mRadiusInPixels = 100;
|
||||||
|
protected double mRadiusInMeters;
|
||||||
|
protected Paint mTextPaint;
|
||||||
|
private ArrayList<MarkerWithLabel> mClonedMarkers;
|
||||||
|
protected boolean mAnimated;
|
||||||
|
int mDensityDpi;
|
||||||
|
|
||||||
|
/** cluster icon anchor */
|
||||||
|
public float mAnchorU = MarkerWithLabel.ANCHOR_CENTER, mAnchorV = MarkerWithLabel.ANCHOR_CENTER;
|
||||||
|
/** anchor point to draw the number of markers inside the cluster icon */
|
||||||
|
public float mTextAnchorU = MarkerWithLabel.ANCHOR_CENTER, mTextAnchorV = MarkerWithLabel.ANCHOR_CENTER;
|
||||||
|
|
||||||
|
public RadiusMarkerClusterer(Context ctx) {
|
||||||
|
super();
|
||||||
|
mTextPaint = new Paint();
|
||||||
|
mTextPaint.setColor(Color.WHITE);
|
||||||
|
mTextPaint.setTextSize(15 * ctx.getResources().getDisplayMetrics().density);
|
||||||
|
mTextPaint.setFakeBoldText(true);
|
||||||
|
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||||
|
mTextPaint.setAntiAlias(true);
|
||||||
|
Drawable clusterIconD = ctx.getResources().getDrawable(R.drawable.marker_cluster);
|
||||||
|
Bitmap clusterIcon = ((BitmapDrawable) clusterIconD).getBitmap();
|
||||||
|
setIcon(clusterIcon);
|
||||||
|
mAnimated = true;
|
||||||
|
mDensityDpi = ctx.getResources().getDisplayMetrics().densityDpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If you want to change the default text paint (color, size, font) */
|
||||||
|
public Paint getTextPaint(){
|
||||||
|
return mTextPaint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the radius of clustering in pixels. Default is 100px. */
|
||||||
|
public void setRadius(int radius){
|
||||||
|
mRadiusInPixels = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set max zoom level with clustering. When zoom is higher or equal to this level, clustering is disabled.
|
||||||
|
* You can put a high value to disable this feature. */
|
||||||
|
public void setMaxClusteringZoomLevel(int zoom){
|
||||||
|
mMaxClusteringZoomLevel = zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Radius-Based clustering algorithm */
|
||||||
|
@Override public ArrayList<StaticCluster> clusterer(MapView mapView) {
|
||||||
|
|
||||||
|
ArrayList<StaticCluster> clusters = new ArrayList<StaticCluster>();
|
||||||
|
convertRadiusToMeters(mapView);
|
||||||
|
|
||||||
|
mClonedMarkers = new ArrayList<MarkerWithLabel>(mItems); //shallow copy
|
||||||
|
while (!mClonedMarkers.isEmpty()) {
|
||||||
|
MarkerWithLabel m = mClonedMarkers.get(0);
|
||||||
|
StaticCluster cluster = createCluster(m, mapView);
|
||||||
|
clusters.add(cluster);
|
||||||
|
}
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StaticCluster createCluster(MarkerWithLabel m, MapView mapView) {
|
||||||
|
GeoPoint clusterPosition = m.getPosition();
|
||||||
|
|
||||||
|
StaticCluster cluster = new StaticCluster(clusterPosition);
|
||||||
|
cluster.add(m);
|
||||||
|
|
||||||
|
mClonedMarkers.remove(m);
|
||||||
|
|
||||||
|
if (mapView.getZoomLevel() > mMaxClusteringZoomLevel) {
|
||||||
|
//above max level => block clustering:
|
||||||
|
return cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<MarkerWithLabel> it = mClonedMarkers.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
MarkerWithLabel neighbour = it.next();
|
||||||
|
double distance = clusterPosition.distanceToAsDouble(neighbour.getPosition());
|
||||||
|
if (distance <= mRadiusInMeters) {
|
||||||
|
cluster.add(neighbour);
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public MarkerWithLabel buildClusterMarker(StaticCluster cluster, MapView mapView) {
|
||||||
|
MarkerWithLabel m = new MarkerWithLabel(mapView, "", null);
|
||||||
|
m.setPosition(cluster.getPosition());
|
||||||
|
m.setInfoWindow(null);
|
||||||
|
m.setAnchor(mAnchorU, mAnchorV);
|
||||||
|
|
||||||
|
Bitmap finalIcon = Bitmap.createBitmap(mClusterIcon.getScaledWidth(mDensityDpi),
|
||||||
|
mClusterIcon.getScaledHeight(mDensityDpi), mClusterIcon.getConfig());
|
||||||
|
Canvas iconCanvas = new Canvas(finalIcon);
|
||||||
|
iconCanvas.drawBitmap(mClusterIcon, 0, 0, null);
|
||||||
|
String text = "" + cluster.getSize();
|
||||||
|
int textHeight = (int) (mTextPaint.descent() + mTextPaint.ascent());
|
||||||
|
iconCanvas.drawText(text,
|
||||||
|
mTextAnchorU * finalIcon.getWidth(),
|
||||||
|
mTextAnchorV * finalIcon.getHeight() - textHeight / 2,
|
||||||
|
mTextPaint);
|
||||||
|
m.setIcon(new BitmapDrawable(mapView.getContext().getResources(), finalIcon));
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void renderer(ArrayList<StaticCluster> clusters, Canvas canvas, MapView mapView) {
|
||||||
|
for (StaticCluster cluster : clusters) {
|
||||||
|
if (cluster.getSize() == 1) {
|
||||||
|
//cluster has only 1 marker => use it as it is:
|
||||||
|
cluster.setMarker(cluster.getItem(0));
|
||||||
|
} else {
|
||||||
|
//only draw 1 Marker at Cluster center, displaying number of Markers contained
|
||||||
|
MarkerWithLabel m = buildClusterMarker(cluster, mapView);
|
||||||
|
cluster.setMarker(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void convertRadiusToMeters(MapView mapView) {
|
||||||
|
|
||||||
|
Rect mScreenRect = mapView.getIntrinsicScreenRect(null);
|
||||||
|
|
||||||
|
int screenWidth = mScreenRect.right - mScreenRect.left;
|
||||||
|
int screenHeight = mScreenRect.bottom - mScreenRect.top;
|
||||||
|
|
||||||
|
BoundingBox bb = mapView.getBoundingBox();
|
||||||
|
|
||||||
|
double diagonalInMeters = bb.getDiagonalLengthInMeters();
|
||||||
|
double diagonalInPixels = Math.sqrt(screenWidth * screenWidth + screenHeight * screenHeight);
|
||||||
|
double metersInPixel = diagonalInMeters / diagonalInPixels;
|
||||||
|
|
||||||
|
mRadiusInMeters = mRadiusInPixels * metersInPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimation(boolean animate){
|
||||||
|
mAnimated = animate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoomOnCluster(MapView mapView, StaticCluster cluster){
|
||||||
|
BoundingBox bb = cluster.getBoundingBox();
|
||||||
|
if (bb.getLatNorth()!=bb.getLatSouth() || bb.getLonEast()!=bb.getLonWest()) {
|
||||||
|
bb = bb.increaseByScale(1.15f);
|
||||||
|
mapView.zoomToBoundingBox(bb, true);
|
||||||
|
} else //all points exactly at the same place:
|
||||||
|
mapView.setExpectedCenter(bb.getCenterWithDateLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onSingleTapConfirmed(final MotionEvent event, final MapView mapView){
|
||||||
|
for (final StaticCluster cluster : reversedClusters()) {
|
||||||
|
if (cluster.getMarker().onSingleTapConfirmed(event, mapView)) {
|
||||||
|
if (mAnimated && cluster.getSize() > 1)
|
||||||
|
zoomOnCluster(mapView, cluster);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.geeksville.mesh.model.map.clustering;
|
||||||
|
|
||||||
|
import org.osmdroid.util.BoundingBox;
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import com.geeksville.mesh.model.map.MarkerWithLabel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster of Markers.
|
||||||
|
* @author M.Kergall
|
||||||
|
*/
|
||||||
|
public class StaticCluster {
|
||||||
|
protected final ArrayList<MarkerWithLabel> mItems = new ArrayList<MarkerWithLabel>();
|
||||||
|
protected GeoPoint mCenter;
|
||||||
|
protected MarkerWithLabel mMarker;
|
||||||
|
|
||||||
|
public StaticCluster(GeoPoint center) {
|
||||||
|
mCenter = center;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(GeoPoint center){
|
||||||
|
mCenter = center;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeoPoint getPosition() {
|
||||||
|
return mCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return mItems.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MarkerWithLabel getItem(int index) {
|
||||||
|
return mItems.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean add(MarkerWithLabel t) {
|
||||||
|
return mItems.add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** set the Marker to be displayed for this cluster */
|
||||||
|
public void setMarker(MarkerWithLabel marker){
|
||||||
|
mMarker = marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the Marker to be displayed for this cluster */
|
||||||
|
public MarkerWithLabel getMarker(){
|
||||||
|
return mMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoundingBox getBoundingBox(){
|
||||||
|
if (getSize()==0)
|
||||||
|
return null;
|
||||||
|
GeoPoint p = getItem(0).getPosition();
|
||||||
|
BoundingBox bb = new BoundingBox(p.getLatitude(), p.getLongitude(), p.getLatitude(), p.getLongitude());
|
||||||
|
for (int i=1; i<getSize(); i++) {
|
||||||
|
p = getItem(i).getPosition();
|
||||||
|
double minLat = Math.min(bb.getLatSouth(), p.getLatitude());
|
||||||
|
double minLon = Math.min(bb.getLonWest(), p.getLongitude());
|
||||||
|
double maxLat = Math.max(bb.getLatNorth(), p.getLatitude());
|
||||||
|
double maxLon = Math.max(bb.getLonEast(), p.getLongitude());
|
||||||
|
bb.set(maxLat, maxLon, minLat, minLon);
|
||||||
|
}
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,7 @@ import org.osmdroid.views.overlay.Polygon
|
||||||
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
|
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
|
||||||
import org.osmdroid.views.overlay.infowindow.InfoWindow
|
import org.osmdroid.views.overlay.infowindow.InfoWindow
|
||||||
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
|
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
|
||||||
|
import com.geeksville.mesh.model.map.clustering.RadiusMarkerClusterer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
|
|
||||||
|
@ -112,11 +113,17 @@ class MapFragment : ScreenFragment("Map Fragment"), Logging {
|
||||||
@Composable
|
@Composable
|
||||||
private fun MapView.UpdateMarkers(
|
private fun MapView.UpdateMarkers(
|
||||||
nodeMarkers: List<MarkerWithLabel>,
|
nodeMarkers: List<MarkerWithLabel>,
|
||||||
waypointMarkers: List<MarkerWithLabel>
|
waypointMarkers: List<MarkerWithLabel>,
|
||||||
|
nodeClusterer: RadiusMarkerClusterer
|
||||||
) {
|
) {
|
||||||
debug("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints")
|
debug("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints")
|
||||||
overlays.removeAll { it is MarkerWithLabel }
|
overlays.removeAll { it is MarkerWithLabel }
|
||||||
overlays.addAll(nodeMarkers + waypointMarkers)
|
// overlays.addAll(nodeMarkers + waypointMarkers)
|
||||||
|
overlays.addAll(waypointMarkers)
|
||||||
|
nodeClusterer.getItems().clear()
|
||||||
|
nodeMarkers.forEach {
|
||||||
|
nodeClusterer.add(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -283,6 +290,8 @@ fun MapView(
|
||||||
val map = rememberMapViewWithLifecycle(context)
|
val map = rememberMapViewWithLifecycle(context)
|
||||||
val state by model.mapState.collectAsStateWithLifecycle()
|
val state by model.mapState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val nodeClusterer = RadiusMarkerClusterer(context)
|
||||||
|
|
||||||
fun MapView.toggleMyLocation() {
|
fun MapView.toggleMyLocation() {
|
||||||
if (context.gpsDisabled()) {
|
if (context.gpsDisabled()) {
|
||||||
debug("Telling user we need location turned on for MyLocationNewOverlay")
|
debug("Telling user we need location turned on for MyLocationNewOverlay")
|
||||||
|
@ -479,6 +488,8 @@ fun MapView(
|
||||||
if (myLocationOverlay != null && overlays.none { it is MyLocationNewOverlay }) {
|
if (myLocationOverlay != null && overlays.none { it is MyLocationNewOverlay }) {
|
||||||
overlays.add(myLocationOverlay)
|
overlays.add(myLocationOverlay)
|
||||||
}
|
}
|
||||||
|
map.overlays.add(nodeClusterer)
|
||||||
|
|
||||||
addCopyright() // Copyright is required for certain map sources
|
addCopyright() // Copyright is required for certain map sources
|
||||||
createLatLongGrid(false)
|
createLatLongGrid(false)
|
||||||
|
|
||||||
|
@ -486,7 +497,7 @@ fun MapView(
|
||||||
}
|
}
|
||||||
|
|
||||||
with(map) {
|
with(map) {
|
||||||
UpdateMarkers(onNodesChanged(nodes), onWaypointChanged(waypoints.values))
|
UpdateMarkers(onNodesChanged(nodes), onWaypointChanged(waypoints.values), nodeClusterer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MapView.zoomToNodes() {
|
fun MapView.zoomToNodes() {
|
||||||
|
|
Ładowanie…
Reference in New Issue