Googleマップのクラスタリング

Googleマップを使用してアプリケーションを開発している場合、左の写真に示すような状況に遭遇する可能性があります。 そして、もしあなたが右の写真がより良く見えると思うなら、あなたはここにいます。



画像



したがって、多数のマーカーを使用する場合にどのような問題が発生しますか:



この問題の解決策は何ですか? 答えはクラスタリングです。 クラスタは、いくつかの同種の要素の組み合わせであり、特定のプロパティを持つ独立したユニットと見なすことができます。 したがって、マーカーを領土ごとに組み合わせて、1つのマーカーに置き換える必要があります。 そして、幸いなことに、これらはすべて既に実装されています。



Googleは、地図を操作するための標準ライブラリに加えて、Google Maps Android Marker Clustering Utilityの素晴らしいライブラリを提供しています。 このライブラリは個別に入手でき、マーカーを作成できます。



いくつかのタイプのマーカーがあり、それぞれに独自のアイコンと必要なクリックアクション(たとえば、テキスト付きのInfoWindow出力)がある場合の最も複雑な例を考えてみましょう。

ドキュメントを読みたい人
developers.google.com/maps/articles/toomanymarkers?hl=en

developers.google.com/maps/documentation/android/utility/marker-clustering

github.com/googlemaps/android-maps-utils





挑戦する


次のタスクを考えてみましょう。流通ネットワークがあり、多くのポイントと、場所をレポートできる商品を運ぶトラックが多数あります。 トラックやアウトレットの状況の監視を確立する必要があります。 トラックマーカーには、ドライバーの顔のアイコンが必要です。 顧客はひどく、クラスターの写真を見て、そうでなければならないと言った。



ステップ1.準備


最初のステップは非常に論理的です-他のすべてが継承される抽象マーカークラスを記述する必要があります。 クラスタリングを使用できるようにするには、このマーカークラスがその場所を返すことができる必要があります。 これを行うには、 ClusterItemインターフェイスを実装する必要があります。 次に、抽象クラスの可能なコード:



public abstract class AbstractMarker implements ClusterItem { protected double latitude; protected double longitude; protected MarkerOptions marker; @Override public LatLng getPosition() { return new LatLng(latitude, longitude); } protected AbstractMarker(double latitude, double longitude) { setLatitude(latitude); setLongitude(longitude); } @Override public abstract String toString(); public abstract MarkerOptions getMarker() { return marker; } public void setMarker(MarkerOptions marker) { this.marker = marker; } //others getters & setters }
      
      







次に、POSマーカーとトラックマーカーのそれほど重要ではないクラスコードがありますが、突然誰かが興味を持ちました。



TradeMarker、TruckMarker
 public class TradeMarker extends AbstractMarker { private static BitmapDescriptor shopIcon = null; private String description; public TradeMarker(String description, double latitude, double longitude) { super(latitude, longitude); setDescription(description); setBitmapDescriptor(); setMarker(new MarkerOptions() .position(new LatLng(getLatitude(), getLongitude())) .title("") .icon(shopIcon)); } public static void setBitmapDescriptor() { if (shopIcon == null) shopIcon = BitmapDescriptorFactory. fromResource(R.drawable.trademarker); } public String toString() { return "Trade place: " + getDescription(); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
      
      







 public class TruckMarker extends AbstractMarker { private String name; private String aim; public TruckMarker(String name, String aim, double latitude, double longitude, BitmapDescriptor photo) { super(latitude, longitude); setName(name); setAim(aim); setMarker(new MarkerOptions() .position(new LatLng(getLatitude(), getLongitude())) .title("") .icon(photo)); } public String toString() { return "Name: " + getName() + "\n" + "Aim: " + getAim(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAim() { return aim; } public void setAim(String aim) { this.aim = aim; } }
      
      









ステップ2.最も単純なクラスタリング


さて、準備が完了したので、今度はクラスタリングを構成します。 ここで最も重要なのはClusterManagerクラスです。これにより、クラスタリングを柔軟に構成できます。 ClusterManagerクラスは、ClusterItemインターフェイスを実装するクラスに渡されるパラメーターとして一般化されます。 初期化中のパラメーターとして、アプリケーションコンテキストとGoogleMapオブジェクトを受け取ります。



したがって、コードでは、次のようになります。



 private ClusterManager<AbstractMarker> clusterManager; //... clusterManager = new ClusterManager<AbstractMarker>(this.getApplicationContext(), getMap());
      
      





そして、 clusterManager.addItem(AbstractMarker marker)メソッドを使用して、このオブジェクトにマーカーを追加するだけです。



ClusterManagerクラスのオブジェクトのクラスターメソッドを使用して、再クラスター化を呼び出すことができます(たとえば、要素を追加した後)。 カメラの位置を変更するときに(ズームを変更するときに)このメソッドを自動的に呼び出すには、 OnCameraChangeListenerハンドラーをマップに割り当てる必要があります。



 map.setOnCameraChangeListener(clusterManager);
      
      







さて、今私たちのカードはほとんど美しいです。 なぜほとんど? ドライバーの写真の代わりにデフォルトでマーカーのみが表示されるためこれがより優れている可能性があります。マーカーをクリックしても、InfoWindowAdapterを作成しても何も起こりません。 ここで、提供された手段を使用して外観を改善します。



ステップ3.アルゴリズム


クラスタリングアルゴリズムを検討してください。 さまざまなアルゴリズムがあり、主に2つのアルゴリズムを区別できます。



グリッドベースのクラスタリング -マップの表示領域は正方形に分割され、1つのクラスターが正方形の中心に配置されます。









距離ベースのクラスタリングはデフォルトのアルゴリズムです。 マーカーの最高濃度の中心の計算に基づいて; クラスターには固定境界がありません。









Googleがドキュメントから撮影した写真に対して私を怒らせないことを願っています。



独自のアルゴリズムを作成する必要がかなりあるとは考えられませんが、本当にしたい場合は、 Algorithmインターフェイスを実装する必要があります。



ステップ4. clusterManagerオブジェクトのプロパティを変更する


私が言ったように、ドライバーの写真は標準のマーカーに変わりました。 注文ではありません。 幸いなことに、これは簡単に修正できます(その他)。 clusterManagerオブジェクトにはsetRendererメソッドがあり、これを使用して、マップなどに出力する前にクラスター/マーカーを管理するクラスを指定できます。 デフォルトでは、多くのチップがすでに正しく実装されているDefaultClusterRendererクラスが使用されます。 したがって、最善のアプローチは、このクラスから継承し、必要なメソッドをオーバーライドすることです。



 public class OwnIconRendered extends DefaultClusterRenderer<AbstractMarker> { public OwnIconRendered(Context context, GoogleMap map, ClusterManager<AbstractMarker> clusterManager) { super(context, map, clusterManager); } @Override protected void onBeforeClusterItemRendered(AbstractMarker item, MarkerOptions markerOptions) { markerOptions.icon(item.getMarker().getIcon()); } }
      
      





そして、このクラスをclusterManagerに割り当てます。



 clusterManager.setRenderer(new OwnIconRendered( getApplicationContext(), getMap(), clusterManager));
      
      







マーカーのアイコンを変更するだけでよいため、 onBeforeClusterItemRenderedメソッドのみを再定義しました。これにより、マップに表示する前にクラスター要素を構成できます。 ただし、オーバーライドする方法の選択は十分に大きくなります。







別の事実として、クラスタリングプロセスでは、 MarkerManagerクラスが重要な役割を果たします。これは、マーカーとクラスターのコレクションを操作するためのインターフェイスを本質的に提供します。



最後に触れましょう:マーカーをクリックすると、toStringメソッドを使用して現在のマーカーに関する情報が標準的に表示されます(再定義しました)。クラスターをクリックすると、この地域にあるコンセントとトラックの数が表示されます。

まず、選択したマーカーとクラスターのオブジェクトが必要です。



 private AbstractMarker chosenMarker; private Cluster<AbstractMarker> chosenCluster;
      
      







clusterManagerにリスナーを追加する必要があります。



 clusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<AbstractMarker>() { @Override public boolean onClusterItemClick(AbstractMarker item) { chosenMarker = item; return false; } }); clusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<AbstractMarker>() { @Override public boolean onClusterClick(Cluster<AbstractMarker> cluster) { chosenCluster = cluster; return false; } });
      
      







また、clusterManagerをOnMarkerClickListenerイベントのリスナーにする必要があります。



 map.setOnMarkerClickListener(clusterManager);
      
      







ここで、InfoWindowAdapterをすべてのトークンとすべてのクラスターに割り当てる必要があります。 これを行うには、メソッドを使用してclusterManagerオブジェクトからマーカークラスターのコレクションを取得します。 また、InfoWindowAdapterをマップに割り当てることを忘れないでください。

 clusterManager.getMarkerCollection().setOnInfoWindowAdapter(new MarkerInfoWindowAdapter()); clusterManager.getClusterMarkerCollection().setOnInfoWindowAdapter(new ClusterInfoWindow()); map.setInfoWindowAdapter(clusterManager.getMarkerManager());
      
      







たとえば、ClusterInfoWindowクラスは次のようになります。



 private class ClusterInfoWindow implements InfoWindowAdapter { @Override public View getInfoContents(Marker arg0) { return null; } @Override public View getInfoWindow(Marker marker) { if (chosenCluster != null) { View v = getLayoutInflater().inflate(R.layout.cluster_window, null); TextView info = (TextView) v.findViewById(R.id.clusterTitle); int[] markerTypesCount = new int[2]; Arrays.fill(markerTypesCount, 0); for (AbstractMarker abstractMarker : chosenCluster.getItems()) { if (abstractMarker instanceof TradeMarker) markerTypesCount[0] += 1; else if (abstractMarker instanceof TruckMarker) markerTypesCount[1] += 1; } info.setText("Trade places: " + markerTypesCount[0] + "\n" + "Truck: " + markerTypesCount[1] + "\n"); return v; } return null; } }
      
      







したがって、私たちは美しく機能的なマップを作成しました。



この記事がお役に立てば幸いです。 それは図書館と個人研究の研究の結果に過ぎないので、私はリソースへのリンクを残しません。 最後まで読んでくれてありがとう!



PS Slack でAndroid開発者の世界最大のコミュニティに参加してください。



All Articles