android-togetherMap
本文我将講解一下我最近實作的高德地圖Marker的聚合功能。在項目開發中需要使用到地圖Marker的聚合功能,但是高德地圖并沒有實作對Marker的聚合功能,是以需要自己實作其聚合功能(這裡說一下百度是實作的,為啥高德不做?)下面我将介紹一下具體的實作步驟。
本項目的具體實作效果如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5iN4ImYmJ2YmBDZmhjMygzY5YWM2Q2MkN2NhZjYkdjMh9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
(一)內建高德地圖API
參考高德地圖開放平台文檔
高德地圖API內建參考位址:高德開放平台
包括配置Manifest,申請AppId等步驟;
添加Layout布局檔案
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
這裡主要是添加MapView控件,該控件是地圖顯示控件,也是我們實作地圖功能的主要控件。
執行Map對象初始化以及各個生命周期方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.map);
mapView.onCreate(savedInstanceState);// 此方法必須重寫
init();
}
private void init() {
if (aMap == null) {
aMap = mapView.getMap();
}
aMap.setOnCameraChangeListener(onCameraChangeListener);
dotList.clear();
dotList = DotInfo.initData();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
updateNormalMarkers();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
主要是在Activity的生命周期方法中添加對MapView的處理...
地圖頁面中添加Marker對象
private void loadMarker(List dotList) {
if (dotList == null || dotList.size() == 0) {
return;
}
for (int i = 0; i < dotList.size(); i++) {
DotInfo dotInfo = dotList.get(i);
MarkerOptions options = new MarkerOptions();
options.anchor(0.5f, 1.0f);
options.position(new LatLng(dotInfo.getDotLat(), dotInfo.getDotLon()));
setIconToOptions(options);
Marker marker = aMap.addMarker(options);
marker.setObject(dotInfo);
marker.setZIndex(ORGZOON);
markerMap.put(dotInfo.getDotId(), marker);
}
}
删除方法中傳遞的參數是我在用戶端寫死的集合資料,執行到這裡至此我們就實作了一個簡單的地圖頁面,并添加了相應的Marker對象,具體效果如下:
(二)添加地圖縮放聚合功能
在上面我們已經實作了一個簡單的地圖顯示頁面以及添加了幾個Marker對象,現在需要實作的效果是當我們擴大地圖的顯示比例的時候,相鄰的Marker會聚合成一個合成的Marker對象,具體的實作效果就是我們一開始展示的效果。
重寫地圖的OnCameraChangeListener
該Listener會在地圖移動或者是改變顯示比例的時候被回調,這裡我們再其回調方法onCameraChangeFinish方法中執行我們的Marker計算,聚合功能,具體代碼如下:
private AMap.OnCameraChangeListener onCameraChangeListener = new AMap.OnCameraChangeListener() {
@Override
public void onCameraChange(CameraPosition cameraPosition) {
}
@Override
public void onCameraChangeFinish(CameraPosition cameraPosition) {
// 放大縮小完成後對聚合點進行重新計算
updateMapMarkers();
}
};
好吧,然後我們看一下自定義方法的updateMapMakers方法的實作邏輯:
private synchronized void updateMapMarkers() {
if (dotList != null && dotList.size() > 0) {
Log.i(TAG, "地圖級别:" + aMap.getCameraPosition().zoom);
// 若目前地圖級别小于初始化比例尺,則顯示聚合網點
if (aMap.getCameraPosition().zoom < ORGZOON) {
markerStatus = MARKER_TOGE;
updateTogMarkers();
}
// 顯示普通marker
else {
if (markerStatus == MARKER_TOGE) {
markerStatus = MARKER_NORMA;
updateNormalMarkers();
}
}
System.gc();
}
}
地圖縮放操作有兩部分,分别為展示普通marker部分和展示聚合marker部分,這裡地圖顯示比例有一個阙值,當我們的顯示比例小于這個阙值的時候計算結果顯示聚合Marker對象,當顯示比例大于這個阙值的時候計算結果顯示普通的Marker對象。
(三)展示聚合Marker操作
下面開始我們就将執行具體的Marker對象的聚合功能了
private void updateTogMarkers() {
Log.i(TAG, "開始顯示聚合網點,清空地圖normal marker...");
aMap.clear();
// 更新聚合marker
MapTogetherManager.getInstance(this, aMap).onMapLoadedUpdateMarker(markerMap);
// 設定marker點選事件,若是聚合網點此時點選marker則放大地圖顯示正常網點
aMap.setOnMarkerClickListener(new AMap.OnMarkerClickListener() {
@Override
public boolean onMarkerClick(Marker marker) {
// 初始化地圖按指定的比例尺移動到定位的坐标
aMap.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(marker.getPosition(), ORGZOON, 3, 0)), 1000, null);
return true;
}
});
}
我們可以發現具體的Marker聚合操作是在MapTogetherManager類中實作的,是以我們需要看一下MaoTigetherManager的實作邏輯。
public void onMapLoadedUpdateMarker(final Map markerMap) {
// 清空記憶體聚合網點資料
togDotInfoMap.clear();
togMarkerMap.clear();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockObject) {
Log.i(TAG, "開始循環周遊,執行網點聚合操作...");
Iterator> iterator = markerMap.entrySet().iterator();
// 循環周遊在已有的聚合基礎上,對添加的單個元素進行聚合
while (iterator.hasNext()) {
assignSingleCluster(iterator.next().getValue());
}
Log.i(TAG, "開始執行将聚合網點展示在地圖上...");
// 将聚合的網點現在在地圖上
if (togDotInfoMap != null && togDotInfoMap.size() > 0) {
Iterator> cIterator = togDotInfoMap.entrySet().iterator();
while (cIterator.hasNext()) {
Map.Entry togMap = cIterator.next();
addTogDotInfoToMap(togMap.getKey(), togMap.getValue());
}
}
}
}
}).start();
}
可以發現在其中我們調用了assignSingleCluster方法對Marker對象進行計算判斷其屬于哪個聚合Marker對象,具體assignSingleCluster方法的實作邏輯:
private void assignSingleCluster(Marker marker) {
DotInfo dotInfo = (DotInfo) marker.getObject();
LatLng latLng = new LatLng(dotInfo.getDotLat(), dotInfo.getDotLon());
Point point = aMap.getProjection().toScreenLocation(latLng);
TogDotInfo togDotInfo = getCluster(point);
if (togDotInfo != null) {
togDotInfo.addClusterItem(marker);
// 更新聚合網點個數
togDotInfo.setDotCount(togDotInfo.getDotCount() + 1);
} else {
togDotInfo = new TogDotInfo(point, latLng);
// 更新聚合網點個數
togDotInfo.setDotCount(1);
togDotInfo.addClusterItem(marker);
togDotInfoMap.put(dotInfo.getDotId() + SystemClock.currentThreadTimeMillis(), togDotInfo);
}
}
好吧,具體的實作邏輯下放到了getCluster方法中:
private TogDotInfo getCluster(Point point) {
Iterator> cIterator = togDotInfoMap.entrySet().iterator();
while (cIterator.hasNext()) {
TogDotInfo togDotInfo = cIterator.next().getValue();
Point poi = togDotInfo.getCenterPoint();
double distance = getDistanceBetweenTwoPoints(point.x, point.y, poi.x, poi.y);
if (distance < CLUSTER_SIZE) {
return togDotInfo;
}
}
return null;
}
這樣經過一系列的操作之後我們就生成了聚合Marker對象清單并将其放置到我們的togDotInfoMap對象中,然後我們調用了addTogDotInfoToMap将聚合Marker對象添加到地圖頁面中。
private void addTogDotInfoToMap(String dotId, TogDotInfo togDotInfo) {
LatLng latlng = togDotInfo.getCenterLatLng();
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.anchor(0.5f, 0.5f).icon(getBitmapDes(togDotInfo)).position(latlng);
Marker marker = aMap.addMarker(markerOptions);
togMarkerMap.put(dotId, marker);
}
這樣我們最終将聚合的Marker對象繪制到了地圖中,這其中繪制聚合Marker對象的樣式都是可定制的,這裡隻是簡單的繪制成一個黑色的圓形,也可以擴充一下,比如當資料大于10的時候一個顔色,當資料大于20的時候一個顔色等等,具體的可參考代碼。
總結:
本文主要是通過自定義的地圖Marker的聚合算法,實作了Marker對象的聚合功能,這裡定制化内容較多就不做成庫了,具體可參考我的:android-togetherMap。另外也可參考我的部落格:快速實作自定義地圖聚合操作