Google Maps APIマップでのマーカーのクラスタリング

こんにちは、Habr! GoogleマップapiおよびReact.jsでクラスター化マーカーを使用してマップを開発した経験についてお話ししたいと思います。 クラスタリングは、1つのクラスター内の近くのマーカー、タグ、ポイントのグループ化です。 これにより、UXが改善され、データが視覚的により明確に重ねられたドットの束よりも表示されます。 私が働いている会社は、メディア用のユニークな製品を作成しています。これはモバイルアプリケーションです。その意味は、写真/ビデオ/ストリーム素材を撮影することであり、編集者が出版物であなたの素材を使用する場合、メディアから優れた報酬を得る機会です。 ユーザーが送信したコンテンツを管理するために、react / reduxスタックでSPAアプリケーションを開発しています。 最近、ユーザーの位置を確認し、近くで興味深いイベントが発生した場合にプッシュ通知を送信できるインタラクティブなマップを作成するタスクに直面しました。



ここに私がしなければならなかったことがあります:







私が最初に思いついたのは、react.jsの既製のソリューションを探すことでした。 上位のgoogle-map-reactライブラリとreact-google-mapsライブラリが2つ見つかりました。 これらは、react.jsのコンポーネントとして提供される標準のGoogleマップAPIのラッパーです。 マーカーとして任意のJSX要素を使用できるため、私の選択はgoogle-map-reactに落ちました。標準のGoogleマップAPIツールでは画像とsvg要素をマーカーとして使用できることを思い出させてください、html構造のトリッキーな挿入を説明するネットワーク上のソリューションがありますマーカーとして使用しますが、google-map-reactはそのまま使用できます。



さらに進むと、レイアウトは、マーカーが互いに近接している場合、グループマーカーに結合されることを示しています。これはクラスタリングです。 readme google-map-reactで、クラスタリングの例を見つけましたが、recomposeを使用して実装されました-これは、関数コンポーネントと高次コンポーネントのラッパーを作成するユーティリティです。 クリエイターは、これは一種の反動だと思うと書いています。 しかし、vryatliの再構成に慣れていない人はすぐにすべてを理解するので、この例を適合させ、不要な依存関係を削除しました。



まず、google-map-reactおよびstateコンポーネントのプロパティを設定し、事前に準備されたマーカーでマップをレンダリングします。

ここで APIキーを取得します



const MAP = { defaultZoom: 8, defaultCenter: { lat: 60.814305, lng: 47.051773 }, options: { maxZoom: 19, }, }; state = { mapOptions: { center: MAP.defaultCenter, zoom: MAP.defaultZoom, }, clusters: [], }; //JSX <GoogleMapReact defaultZoom={MAP.defaultZoom} defaultCenter={MAP.defaultCenter} options={MAP.options} onChange={this.handleMapChange} yesIWantToUseGoogleMapApiInternals bootstrapURLKeys={{ key: 'yourkey' }} > {this.state.clusters.map(item => { if (item.numPoints === 1) { return ( <Marker key={item.id} lat={item.points[0].lat} lng={item.points[0].lng} /> ); } return ( <ClusterMarker key={item.id} lat={item.lat} lng={item.lng} points={item.points} /> ); })} </GoogleMapReact>
      
      





this.state.clusters配列が空なので、マップ上にマーカーはありません。 マーカーをグループに結合するには、 スーパークラスターライブラリを使用します



例として、座標を持つポイントを生成します:



 const TOTAL_COUNT = 200; export const susolvkaCoords = { lat: 60.814305, lng: 47.051773 }; export const markersData = [...Array(TOTAL_COUNT)] .fill(0) // fill(0) for loose mode .map((__, index) => ({ id: index, lat: susolvkaCoords.lat + 0.01 * index * Math.sin(30 * Math.PI * index / 180) * Math.cos(50 * Math.PI * index / 180) + Math.sin(5 * index / 180), lng: susolvkaCoords.lng + 0.01 * index * Math.cos(70 + 23 * Math.PI * index / 180) * Math.cos(50 * Math.PI * index / 180) + Math.sin(5 * index / 180), }));
      
      





マップの縮尺/中心の変更ごとに、クラスターを再カウントします。



 handleMapChange = ({ center, zoom, bounds }) => { this.setState( { mapOptions: { center, zoom, bounds, }, }, () => { this.createClusters(this.props); } ); }; createClusters = props => { this.setState({ clusters: this.state.mapOptions.bounds ? this.getClusters(props).map(({ wx, wy, numPoints, points }) => ({ lat: wy, lng: wx, numPoints, id: `${numPoints}_${points[0].id}`, points, })) : [], }); }; getClusters = () => { const clusters = supercluster(markersData, { minZoom: 0, maxZoom: 16, radius: 60, }); return clusters(this.state.mapOptions); };
      
      





getClustersメソッドでは、生成されたポイントをスーパークラスターに供給し、出力はクラスターです。 したがって、スーパークラスターは、その隣にあるポイントの座標を単純に組み合わせ、入力されたすべてのポイントがある座標とポイント配列を持つ新しいポイントを生成しました。



ここでデモを見ることができます。

例のソースコードはこちらです。



All Articles