Как получить листья из кластера, используя собственный мапбокс React?

В настоящее время я работаю над проектом, который требует от меня использования react-native. Проблема, с которой я столкнулся, кажется простой, учитывая то, как это решается в SDK Mapbox для Android. Ситуация следующая:

Я показываю полноэкранную карту с некоторыми кнопками для масштабирования и центрирования по местоположению пользователя. На этой карте есть несколько «достопримечательностей». В настоящее время я группирую их, используя ShapeSource для данных и SymbolLayer для одиночных и SymbolLayer для сгруппированных «точек интереса».

Я пытаюсь получить доступ ко всем листьям одного из кластеров, на которые я нажимаю. В Android SDK этого можно добиться с помощью следующего кода:

 String[] layers = new String[] {"cluster-route-0", "cluster-route-1", "cluster-route-2", "cluster-route-3", "cluster-route-4"};


 features = mMapboxMap.queryRenderedFeatures(pixel, layers);
 GeoJsonSource source = mMapboxMap.getStyle().getSourceAs(ROUTE_SOURCE_ID);

Затем я бы перебирал все функции и получал листья кластера вот так

for (Feature feature : features) {
   FeatureCollection collection = source.getClusterLeaves(feature, 100,0);
}

Тогда переменной коллекции будет FeatureCollection, которая содержит все функции или листья, содержащиеся в кластере.

Просматривая документацию, я ничего не нашел аналогично варианту React Native Mapbox. Поэтому мне было интересно, сможет ли кто-нибудь здесь найти решение для этого. Или, может быть, я что-то пропустил либо в примерах, либо в документации.


person MikeSli    schedule 15.08.2019    source источник


Ответы (1)


Недавно я смог глубже взглянуть на описанную выше проблему и фактически пришел к решению, которое было достаточно хорошим для моего варианта использования. Есть несколько вариантов решения этой проблемы.

1) Использование другого кластерного решения в сочетании с Mapbox, например, Supercluster

2) Создайте репро Mapbox и добавьте метод, который вызывает метод getClusterLeaves() из собственного SDK.

Решение 1

Первое решение использует пакет Supercluster npm, и это решение полностью основано на информации, найденной на эту страницу. Я поделюсь кодом, которым я закончил, но для получения дополнительной информации я хотел бы предложить вам прочитать связанный ресурс.

//Initialize supercluster, these values worked great for me
const cluster = new Supercluster({ radius: 40, maxZoom: 16 });
cluster.load(collection.features);

Переменная коллекции, используемая в методе загрузки суперкластера, должна представлять значение FeatureCollection. Которые можно создать несколькими способами. Тот, который мне понравился, был ниже.

const collection = MapboxGL.geoUtils.makeFeatureCollection(groupFeatures);

Переменная groupFeatures может быть массивом, состоящим из объектов, отображаемых на карте. Для облегчения доступа я сохранил переменную кластера в моем состоянии, прежде чем двигаться дальше. Мне нравится хранить вещи отдельно, чтобы мне было легко увидеть, какая часть кода за что отвечает. Итак, мой метод рендеринга выглядит примерно так, как показано ниже.

<MapboxGL.MapView
  ref={c => (this._map = c)}
  onRegionDidChange={this.updateClusters}
  zoomEnabled
  style={[{ flex: 1 }]}>

 //This is where we render the clusters
 {this.renderPoints()}

</MapboxGL.MapView>

Следующим шагом является получение фактических кластеров с помощью суперкластера, для этого нам нужны границы отображаемой карты и текущий уровень масштабирования (с использованием Math.round, поскольку десятичные значения могут привести к ошибкам в суперкластере).

const bounds = await this._map.getVisibleBounds();
const zoom = Math.round(await this._map.getZoom());
const westLng = bounds[1][0];
const southLat = bounds[1][1];
const eastLng = bounds[0][0];
const northLat = bounds[0][1];

Теперь мы можем получать фактические кластеры, делая что-то вроде такого:

const sc = this.state.superCluster;
const clusters = sc.getClusters(
    [westLng, southLat, eastLng, northLat],
    zoom
)
this.setState({clusters: clusters})

И теперь мы можем визуализировать кластеры в Mapbox Shapesource.

renderPoints = () => {
  const { clusters } = this.state;
  return (
   <MapboxGL.ShapeSource
     id="symbolLocationSource"
     hitbox={{ width: 18, height: 18 }}
     onPress={this.onMarkerSelected}
     shape={{ type: "FeatureCollection", features: superClusterClusters }}
    >
    <MapboxGL.SymbolLayer
     id="pointCount"
     minZoomLevel={6}
     style={mapStyles.clusterCount}
    />

    <MapboxGL.CircleLayer
     id="clusteredPoints"
     minZoomLevel={6}
     belowLayerID="pointCount"
     filter={[">", "point_count", 1]}
     style={mapStyles.clusteredPoints}
    />

    <MapboxGL.SymbolLayer
     id="symbolLocationSymbols"
     minZoomLevel={6}
     filter={["!", ["has", "point_count"]]}
     style={mapStyles.icon}
    />
   </MapboxGL.ShapeSource>
 );
}

Итак, теперь последний кусок головоломки, мы можем получить фактические листья кластера. В последней части приведенного выше кода я уже добавил метод onMarkerPressed для onPress. Здесь мы получаем листья кластера.

onMarkerSelected = event => {
  const point = event.nativeEvent.payload;
  const { name, cluster } = point.properties;

  if (cluster) {
    const sc = this.state.superCluster;
    const points = sc
      .getLeaves(point.properties.cluster_id, Infinity)
    //This will output an array with all leaves of the selected cluster
    console.log(points);
  }
}

Решение 2

Таким образом, упомянутое выше решение - это решение, которое я выбрал для моей собственной ситуации. Поэтому я не исследовал решение разветвления репродукции Mapbox и добавления к нему функциональности. Однако я нашел несколько указателей, которые помогут вам начать работу по ссылке здесь и здесь

person MikeSli    schedule 16.10.2019