Ответ 1
Google предоставила утилиту для этого в составе своей утилиты Google Maps Android API:
https://developers.google.com/maps/documentation/android/utility/marker-clustering
с исходным кодом:
Мне нужно поместить различные маркеры в GoogleMap из API Google Maps Android v2. Проблема состоит в том, что несколько маркеров установлены в одну и ту же позицию (lat/lng), поэтому пользователь видит только "новейший" маркер.
Есть ли возможность (в лучшем случае: библиотека), которая кластерирует разные маркеры из одной и той же области (относительно масштабирования)?
Я уже читал о MarkerClusterer, но он предназначен для JavaScript API.
Google предоставила утилиту для этого в составе своей утилиты Google Maps Android API:
https://developers.google.com/maps/documentation/android/utility/marker-clustering
с исходным кодом:
У меня еще не было возможности попробовать это, но это выглядит очень многообещающе:
http://code.google.com/p/android-maps-extensions/
Вот еще одна библиотека с довольно круто выглядящими анимациями кластера:
Я немного опаздываю, но Clusterkraf - отличная библиотека, проверьте это:
Для тех, кто ищет собственный код кластеризации. Пожалуйста, проверьте мой кластерный алгоритм очень быстро и прекрасно работает.
Я посмотрел на различные библиотеки и нашел их настолько сложными, что не мог понять ни слова, поэтому решил создать свой собственный алгоритм кластеризации. Вот мой код в java.
static int OFFSET = 268435456;
static double RADIUS = 85445659.4471;
static double pi = 3.1444;
public static double lonToX(double lon) {
return Math.round(OFFSET + RADIUS * lon * pi / 180);
}
public static double latToY(double lat) {
return Math.round(OFFSET
- RADIUS
* Math.log((1 + Math.sin(lat * pi / 180))
/ (1 - Math.sin(lat * pi / 180))) / 2);
}
public static int pixelDistance(double lat1, double lon1, double lat2,
double lon2, int zoom) {
double x1 = lonToX(lon1);
double y1 = latToY(lat1);
double x2 = lonToX(lon2);
double y2 = latToY(lat2);
return (int) (Math
.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2))) >> (21 - zoom);
}
static ArrayList<Cluster> cluster(ArrayList<Marker> markers, int zoom) {
ArrayList<Cluster> clusterList = new ArrayList<Cluster>();
ArrayList<Marker> originalListCopy = new ArrayList<Marker>();
for (Marker marker : markers) {
originalListCopy.add(marker);
}
/* Loop until all markers have been compared. */
for (int i = 0; i < originalListCopy.size();) {
/* Compare against all markers which are left. */
ArrayList<Marker> markerList = new ArrayList<Marker>();
for (int j = i + 1; j < markers.size();) {
int pixelDistance = pixelDistance(markers.get(i).getLatitude(),
markers.get(i).getLongitude(), markers.get(j)
.getLatitude(), markers.get(j).getLongitude(),
zoom);
if (pixelDistance < 40) {
markerList.add(markers.get(j));
markers.remove(j);
originalListCopy.remove(j);
j = i + 1;
} else {
j++;
}
}
if (markerList.size() > 0) {
markerList.add(markers.get(i));
Cluster cluster = new Cluster(clusterList.size(), markerList,
markerList.size() + 1, originalListCopy.get(i)
.getLatitude(), originalListCopy.get(i)
.getLongitude());
clusterList.add(cluster);
originalListCopy.remove(i);
markers.remove(i);
i = 0;
} else {
i++;
}
/* If a marker has been added to cluster, add also the one */
/* we were comparing to and remove the original from array. */
}
return clusterList;
}
Just pass in your array list here containing latitude and longitude then to display clusters. Here goes the function.
@Override
public void onTaskCompleted(ArrayList<FlatDetails> flatDetailsList) {
LatLngBounds.Builder builder = new LatLngBounds.Builder();
originalListCopy = new ArrayList<FlatDetails>();
ArrayList<Marker> markersList = new ArrayList<Marker>();
for (FlatDetails detailList : flatDetailsList) {
markersList.add(new Marker(detailList.getLatitude(), detailList
.getLongitude(), detailList.getApartmentTypeString()));
originalListCopy.add(detailList);
builder.include(new LatLng(detailList.getLatitude(), detailList
.getLongitude()));
}
LatLngBounds bounds = builder.build();
int padding = 0; // offset from edges of the map in pixels
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding);
googleMap.moveCamera(cu);
ArrayList<Cluster> clusterList = Utils.cluster(markersList,
(int) googleMap.getCameraPosition().zoom);
// Removes all markers, overlays, and polylines from the map.
googleMap.clear();
// Zoom in, animating the camera.
googleMap.animateCamera(CameraUpdateFactory.zoomTo(previousZoomLevel),
2000, null);
CircleOptions circleOptions = new CircleOptions().center(point) //
// setcenter
.radius(3000) // set radius in meters
.fillColor(Color.TRANSPARENT) // default
.strokeColor(Color.BLUE).strokeWidth(5);
googleMap.addCircle(circleOptions);
for (Marker detail : markersList) {
if (detail.getBhkTypeString().equalsIgnoreCase("1 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk1)));
} else if (detail.getBhkTypeString().equalsIgnoreCase("2 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_2)));
}
else if (detail.getBhkTypeString().equalsIgnoreCase("3 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_3)));
} else if (detail.getBhkTypeString().equalsIgnoreCase("2.5 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk2)));
} else if (detail.getBhkTypeString().equalsIgnoreCase("4 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_4)));
} else if (detail.getBhkTypeString().equalsIgnoreCase("5 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk5)));
} else if (detail.getBhkTypeString().equalsIgnoreCase("5+ BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_5)));
}
else if (detail.getBhkTypeString().equalsIgnoreCase("2 BHK")) {
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(detail.getLatitude(), detail
.getLongitude()))
.snippet(String.valueOf(""))
.title("Flat" + flatDetailsList.indexOf(detail))
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.bhk_2)));
}
}
for (Cluster cluster : clusterList) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.cluster_marker, options);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.white));
paint.setTextSize(30);
canvas.drawText(String.valueOf(cluster.getMarkerList().size()), 10,
40, paint);
googleMap.addMarker(new MarkerOptions()
.position(
new LatLng(cluster.getClusterLatitude(), cluster
.getClusterLongitude()))
.snippet(String.valueOf(cluster.getMarkerList().size()))
.title("Cluster")
.icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
}
}
Вам необходимо выполнить следующие действия:
Студия Android/ Gradle:
dependencies {
compile 'com.google.maps.android:android-maps-utils:0.3+'
}
Добавить простой кластер кластера
Выполните следующие шаги, чтобы создать простой кластер из десяти маркеров. результат будет выглядеть так, хотя количество маркеров показанный/сгруппированный, изменится в зависимости от уровня масштабирования:
Ниже приведено краткое описание необходимых шагов:
ClusterItem
для отображения маркера на карте. Элемент кластера возвращает позицию маркера в качестве объекта LatLng.ClusterManager
для группировки элементов кластера (маркеров) на основе уровня масштабирования.OnCameraChangeListener()
в ClusterManager
, так как ClusterManager
реализует прослушиватель.OnMarkerClickListener()
значение ClusterManager
, так как ClusterManager
реализует прослушиватель.ClusterManager
.Глядя на шаги более подробно: Чтобы создать наш простой кластер из десяти маркеров, сначала создайте класс MyItem
, который реализует ClusterItem
.
public class MyItem implements ClusterItem {
private final LatLng mPosition;
public MyItem(double lat, double lng) {
mPosition = new LatLng(lat, lng);
}
@Override
public LatLng getPosition() {
return mPosition;
}
}
В своем действии на карте добавьте ClusterManager
и подайте ему элементы кластера. Обратите внимание на аргумент типа <MyItem>
, который объявляет ClusterManager типом MyItem
.
private void setUpClusterer() {
// Declare a variable for the cluster manager.
private ClusterManager<MyItem> mClusterManager;
// Position the map.
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));
// Initialize the manager with the context and the map.
// (Activity extends context, so we can pass 'this' in the constructor.)
mClusterManager = new ClusterManager<MyItem>(this, getMap());
// Point the map listeners at the listeners implemented by the cluster
// manager.
getMap().setOnCameraChangeListener(mClusterManager);
getMap().setOnMarkerClickListener(mClusterManager);
// Add cluster items (markers) to the cluster manager.
addItems();
}
private void addItems() {
// Set some lat/lng coordinates to start with.
double lat = 51.5145160;
double lng = -0.1270060;
// Add ten cluster items in close proximity, for purposes of this example.
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
lat = lat + offset;
lng = lng + offset;
MyItem offsetItem = new MyItem(lat, lng);
mClusterManager.addItem(offsetItem);
}
}
Demo
https://www.youtube.com/watch?v=5ZnVraO1mT4
Github
Если вам нужна дополнительная информация/завершенный проект, посетите: https://github.com/dimitardanailov/googlemapsclustermarkers
Если у вас есть еще вопросы, свяжитесь со мной.
Для тех, кто испытывает проблемы с Clusterkraf (я не могу заставить его работать!), и для тех, кто не может использовать расширение Android-карт, он отправляется с помощью пользовательской сборки библиотеки Play Services, я хочу предложить эту другую библиотека, которая крошечная, хорошо написана и работает из коробки:
Clusterer: https://github.com/mrmans0n/clusterer
После целого утра, потраченного на использование двух других библиотек, это спасло мой день!
если вы получаете только самый новый маркер, вам нужно сгруппировать все маркеры
> private ClusterManager<MyItem> mClusterManager;
после добавления ваших элементов
mClusterManager.addItem("your item");
mClusterManager.cluster();
Для тех из вас, кто испытывает проблемы с работой утилиты Google Maps Android API Utility Library, мы создали собственную библиотеку быстрой кластеризации: https://github.com/sharewire/google-maps-clustering. Он может легко обрабатывать 20000 маркеров на карте.