1

I am using @react-native-mapbox-gl/maps and I want to implement clustering for markers. I couldn't find any solution for my implementation. Attach image will show that two markers should be combined but they are not.

enter image description here

Below I am pasting my code:

<MapboxGL.MapView
      showUserLocatin={true}
      zoomLevel={10}
      zoomEnabled={zoomEnabled}
      pitchEnabled={true}
      onPress={onMapPress}
      onRegionIsChanging={onRegionIsChanging}
      surfaceView={true}
      rotateEnabled={rotateEnabled}
      compassEnabled={false}
      showUserLocation={false}
      userTrackingMode={MapboxGL.UserTrackingModes.None}
      scrollEnabled={scrollEnabled}
      styleURL={styleURL}
      centerCoordinate={getFocusPoint() || getStartingPoint()}
      ref={(c) => (_map = c)}
      onRegionDidChange={onRegionChange}
      style={style}
      cluster
    >
      {renderLines()}
      <MapboxGL.SymbolLayer
        id={'abc'}
        sourceID={MapboxGL.StyleSource.DefaultSourceID}
      />
      <MapboxGL.Camera
        zoomLevel={zoom}
        centerCoordinate={getFocusPoint() || getStartingPoint()}
      />
      {(simplePlaceData?.length > 0 || places?.length > 0) && renderMarkers()}
    </MapboxGL.MapView>

Below is our renderMarkers function( basically I am displaying any RN component like image/icon inside MapboxGL.PointAnnotation):

const renderMarkers = () => {
    if (simplePlaceData)
      return simplePlaceData?.map((_place) => {
        const {lat, lng, id} = _place
        const latVal = parseFloat(lat)
        const lngVal = parseFloat(lng)
        if (!lat || !lng || isNaN(latVal) || isNaN(lngVal)) return null

                return (
                    <MapboxGL.PointAnnotation
                        key={`${id}`}
                        id={`${id}`}
                        title={`${lat}-${lng}`}
                        coordinate={[parseFloat(lng), parseFloat(lat)]}>
                     <Col style={styles.mapMarkers}>
              <Icon
                name={'map-marker'}
                family={'materialCommunity'}
                color={Colors.linkBlue}
                size={31}
              />
            </Col>
                    </MapboxGL.PointAnnotation>
                )
            })
        else
            return places?.length > 0 &&  places.map(_place => {
                const {lat, lng, id, image, name} = _place.trip_place.place
                const isSelected = (getFocusPoint() || getStartingPoint())?.first() == lng &&
                    (getFocusPoint() || getStartingPoint())?.last() == lat
                if (Platform.OS === 'ios') {
                    return (
                        <MapboxGL.PointAnnotation
                            key={`${id}`}
                            id={`${id}`}
                            title={name}
                            coordinate={[parseFloat(lng), parseFloat(lat)]}
                        >
                            <MapMarker
                                image={{uri: image}}
                                imageSize={isSelected ? 41 : 31}
                                style={isSelected ? styles.mapMarkersSelected : styles.mapMarkers}
                                onPress={() => selectPlace(_place.trip_place.place, true)}
                            />
                        </MapboxGL.PointAnnotation>
                    )
                } else {
                    return (
                        <MapboxGL.MarkerView
                            id={`${id}`}
                            key={`${id}`}
                            coordinate={[parseFloat(lng), parseFloat(lat)]}
                            title={name}
                        >
                            <View style={isSelected ? styles.mapMarkerContainerSelected : styles.mapMarkerContainer}>
                                <MapMarker
                                    image={{uri: image}}
                                    imageSize={isSelected ? 41 : 31}
                                    style={isSelected ? styles.mapMarkersSelected : styles.mapMarkers}
                                    onPress={() => selectPlace(_place.trip_place.place, true)}
                                />
                            </View>
                        </MapboxGL.MarkerView>
                    )
                }
            })
    }

Is there any solution to to apply for MapboxGL.PointAnnotation to show markers as a combined cluster with number of items inside? Or there is anothe component of MapboxGL which I can use to achieve this functionality?

Thanks

Adnan Ali
  • 2,890
  • 5
  • 29
  • 50

1 Answers1

9

So from my experience with React Native Mapbox GL, you can't use point annotations for clustering. You'll have to use icons. One rule you have to keep in mind for this to work is that your markers have to be GEO JSON features collection. Checkout the link below if you don't know what that is. https://enterprise.arcgis.com/en/geoevent/latest/ingest/GUID-F489B3D2-74DB-4EA2-8A4E-330628193843-web.png Once you have your feature collection, you feed it into the Shapsource and clusters should start showing up.

 <MapboxGL.ShapeSource
            ref={shapeSource}
            shape={{ type: 'FeatureCollection', features: [...''] }}
            id="symbolLocationSource"
            hitbox={{ width: 18, height: 18 }}
            onPress={async point => {
              if (point.features[0].properties.cluster) {
                const collection = await shapeSource.current.getClusterLeaves(
                  point.features[0],
                  point.features[0].properties.point_count,
                  0,
                )
                // Do what you want if the user clicks the cluster
                console.log(collection)
              } else {
                // Do what you want if the user clicks individual marker

                console.log(point.features[0])
              }
            }}
            clusterRadius={50}
            clusterMaxZoom={14}
            cluster
          >

In order to get individual pictures for markers to show up once you zoom in; You'll need to get the image from the individual marker. So if you have a feature collection, each feature should have an image, you could either use an image stored in your project folder and replace the iconImage property in the symbol. Or if your feature has a link to an image, you could use the property in the feature like so:

 iconImage: ['get', '___ whatever name you gave the link___'],

 <MapboxGL.SymbolLayer
              id="singlePoint"
              filter={['!', ['has', 'point_count']]}
              style={{
                iconImage: ['get', '___ whatever name you gave the link___'],
                iconSize: 0.3,
                iconHaloColor: 'black',
                iconHaloWidth: 10,
                iconColor: 'white',
                iconHaloColor: 'black',
                iconHaloWidth: 400,
                iconAllowOverlap: true,
              }}
            />

in order to get that to show up you need mapbox images

  <MapboxGL.Images
            images={images}
            onImageMissing={async url => {
              setImages({ ...images, [url]: { uri: await getImage(url) } })
            }}
  />

So that get request we did with the link, will call the mapbox images. Just make sure you have an images, and setImages in your state. This will then allow you to show the current image of your point annotation. Only problem is that it's hard to edit, so you can't just make them appear as circles unless they're cropped that way.

   <MapboxGL.MapView
          style={styles.map}
          ref={mapRef}
          styleURL="___ url___"
          zoomEnabled
        >
          <MapboxGL.Camera
            animationDuration={250}
            ref={ref => (this.camera = ref)}
            minZoomLevel={5}
            zoomLevel={6}
            maxZoomLevel={20}
            animationMode="flyTo"
            centerCoordinate={currrentLocation}
            Level={stateZoomLevel}
          />
   
          <MapboxGL.Images
            images={images}
            onImageMissing={async url => {
              setImages({ ...images, [url]: { uri: await getImage(url) } })
            }}
          />
          {/* Cluster Individual Drop View */}
          <MapboxGL.ShapeSource
            ref={shapeSource}
            shape={{ type: 'FeatureCollection', features: [...''] }}
            id="symbolLocationSource"
            hitbox={{ width: 18, height: 18 }}
            onPress={async point => {
              if (point.features[0].properties.cluster) {
                const collection = await shapeSource.current.getClusterLeaves(
                  point.features[0],
                  point.features[0].properties.point_count,
                  0,
                )
                // Do what you want if the user clicks the cluster
                console.log(collection)
              } else {
                // Do what you want if the user clicks individual marker

                console.log(point.features[0])
              }
            }}
            clusterRadius={50}
            clusterMaxZoom={14}
            cluster
          >
            <MapboxGL.SymbolLayer
              id="pointCount"
              style={layerStyles.clusterCount}
            />
            <MapboxGL.UserLocation
              visible
              onUpdate={location => {
                setCurrentLocation({
                  latitude: location.coords.latitude,
                  longitude: location.coords.longitude,
                })
              }}
            />
            <MapboxGL.CircleLayer
              id="clusteredPoints"
              belowLayerID="pointCount"
              filter={['has', 'point_count']}
              style={{
                circlePitchAlignment: 'map',
                circleColor: '#A59ADD',
                circleRadius: [
                  'step',
                  ['get', 'point_count'],
                  20,
                  100,
                  25,
                  250,
                  30,
                  750,
                  40,
                ],
                circleOpacity: 0.84,
                circleStrokeWidth: 0,
                circleStrokeColor: 'blue',
              }}
            />
            <MapboxGL.SymbolLayer
              id="singlePoint"
              filter={['!', ['has', 'point_count']]}
              style={{
                iconImage: ['get', '__image name___'],
                iconSize: 0.3,
                iconHaloColor: 'black',
                iconHaloWidth: 10,
                iconColor: 'white',
                iconHaloColor: 'black',
                iconHaloWidth: 400,
                iconAllowOverlap: true,
              }}
            />
          </MapboxGL.ShapeSource>
        </MapboxGL.MapView>

const layerStyles = {
  singlePoint: {
    circleColor: 'green',
    circleOpacity: 0.84,
    circleStrokeWidth: 2,
    circleStrokeColor: 'white',
    circleRadius: 5,
    circlePitchAlignment: 'map',
  },
  clusteredPoints: {},
  clusterCount: {
    textField: '{point_count}',
    textSize: 12,
    textPitchAlignment: 'map',
  },
}

If this helped upvote!

ouflak
  • 2,458
  • 10
  • 44
  • 49
Amissi
  • 167
  • 2
  • 12
  • I found this to be very helpful, thank you! Would you mind posting a fully working example if you eventually got it to work? I would very much appreciate it. – Griffin Baker Jan 22 '23 at 20:09