Кластеризация маркеров на карте Google Maps API

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Привет, Хабр! Хочу рассказать о моем опыте разработки карты с кластеризованными маркерами на google maps api и React.js. Кластеризация — это группировка близлежащих маркеров, меток, точек в один кластер. Это помогает улучшить UX и отобразить данные визуально понятнее, чем куча наехавших друг на друга точек. Компания, в которой я работаю, создает уникальный продукт для СМИ, это мобильное приложение, смысл которого заключается в съемке фото/видео/стрим материалов и возможности получить отличную компенсацию от СМИ в том случае, если редакция использует ваш материал в публикации. Я занимаюсь разработкой SPA приложения на стеке react/redux для модерации контента, присылаемого пользователями. Недавно передо мной встала задача сделать интерактивную карту на которой можно было бы увидеть местоположение пользователей и отправить им push уведомление, если поблизости происходит интересное событие.

Вот что мне предстояло сделать:

Первое что пришло мне на ум, поискать готовое решение для react.js. Я нашел 2 топовых библиотеки google-map-react и react-google-maps. Они представляют собой обертки над стандартным API Google maps, представленные в виде компонент для react.js. Мой выбор пал на google-map-react потому-что она позволяла использовать в качестве маркера любой JSX элемент, напомню что стандартные средства google maps api позволяют использовать в качестве маркера изображение и svg элемент, в сети есть решения, описывающие хитрую вставку html конструкций в качестве маркера, но google-map-react представляет это из коробки.

Едем дальше, на макете видно что если маркеры находятся близко к друг другу, они объединяются в групповой маркер — это и есть кластеризация. В readme google-map-react я нашел пример кластеризации, но он был реализован с помощью recompose — это утилита, которая создает обертку над function components и higher-order components. Создатели пишут чтобы мы думали что это некий lodash для реакта. Но тем, кто незнаком с recompose врятли сразу будет все понятно, поэтому я адаптировал этот пример и убрал лишнюю зависимость.

Для начала зададим свойства для google-map-react и state компоненты, отрендерим карту с заранее подготовленными маркерами:
(api key получаем здесь)

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 пустой. Для объединения маркеров в группу используем библиотеку supercluster

Для примера сгенерируем точки с координатами:

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 мы скармливаем сгенерированные точки в supercluster, и на выходе получаем кластеры. Таким образом supercluster просто объединил лежащие рядом координаты точек и выдал новую точку со своими координатами и массивом points, где лежат все вошедшие точки.

Демо можно посмотреть здесь
Исходный код примера здесь

Источник: habrahabr.ru

Комментарии: