react native mapbox dynamically added PointAnnotations are misplaced

Viewed 3360

I currently developing a react native app ( version 0.55.2) and mapbox/react-native (version 6.1.2-beta2) I have a situation where some annotations are shown initially on map render, then further annotations are loaded when the user's zooms. The first annotations are displayed at the right place.

However, when new annotations are added, there are all stuck at the top left corner.

Following their documentation, https://github.com/mapbox/react-native-mapbox-gl/blob/master/docs/MapView.md, I tried to call the function when the map is loaded or rendered. I even tried a setTimeout. The annotations always appears at the topleft map.

Any ideas how should I approach this? THanks!
class map extends React.Component {
  constructor(props) {
    super(props);
    this.getMapVisibleBounds = getMapVisibleBounds.bind(this);
    this.state = {
      ...INIT_MAP_STATE
    };
  }
  //compo lifecyle
  componentDidUpdate(prevProps, prevState) {


    if (this.state.userPosition.longitude !== prevState.userPosition.longitude) {
      this.setBounds();//first annotations. works fine
    }
    if (this.state.zoomLevel !== prevState.zoomLevel) {
     this.setBounds(); //update annotations. doesn't work
    }


  }


render()=>{
const { quest, checkpoint } = this.props;
    const { selectedIndex } = this.state;
    return (
      <View style={styles.container}>
        <Mapbox.MapView
          styleURL={MAP_STYLE}
          zoomLevel={this.state.zoomLevel}
          centerCoordinate={[this.state.userPosition.longitude, 
this.state.userPosition.latitude]}
          style={styles.mapWrap}
        >
          {this.renderMap(checkpoint, "checkpoint")}
        </Mapbox.MapView>

      </View>
    );
}
setBounds = () => {
this.getMapVisibleBounds(this.map)
  .catch(err => {
    console.error(err);
  })
  .then(bounds => {
    this._setMapBounds(bounds);// set state bounds
    return this.props.onLoadQuest(bounds); //api call
  });
 }
}

// annotations rendering

class checkPoint extends Component {
  constructor(props) {
    super(props);
  }



  renderAnnotations = (data, id) => {
    const uniqKey = `checkpoint_${id}`;
    return (
      <Mapbox.PointAnnotation key={uniqKey} id={uniqKey} coordinate={[data[0], data[1]]}>
        <TouchableWithoutFeedback onPress={idx => this.onSelect(id)}>
          <Image source={checkPointImg} style={styles.selfAvatar} resizeMode="contain" />
        </TouchableWithoutFeedback>
      </Mapbox.PointAnnotation>
    );
  };

  render() {
    if (!this.props.checkpoint || isEmpty(this.props.checkpoint)) {
      return null;
    }
    const { hits } = this.props.checkpoint;
    if (!Array.isArray(hits)) {
      return [];
    }

    return hits.map((c, idx) =>
      this.renderAnnotations(c._source.location.coordinates, c._source.id)
    );
  }
}
3 Answers

"PointAnnotation" is legacy, try passing your points to as an object. You're map render will be so much faster once you make the swap. Something like this.

<MapboxGL.MapView
    centerCoordinate={[ userLocation.longitude, userLocation.latitude ]}
    pitchEnabled={false}
    rotateEnabled={false}
    style={{ flex: 1 }}
    showUserLocation={true}
    styleURL={'your_style_url'}
    userTrackingMode={MapboxGL.UserTrackingModes.MGLUserTrackingModeFollow}
    zoomLevel={10}
>

    <MapboxGL.ShapeSource
        key='icon'
        id='icon'
        onPress={this._onMarkerPress}
        shape={{type: "FeatureCollection", features: featuresObject }}
        type='geojson'
        images={images}
    >
        <MapboxGL.SymbolLayer 
            id='icon'
            style={layerStyles.icon}
        />
    </MapboxGL.ShapeSource>
</MapboxGL.MapView>

Where "featuresObject" looks something like this...

        let featuresObject = []

        annotation.forEach((annot, index) => {
            let lat = annot.latitude
            let lng = annot.longitude


            featuresObject[index] = {
                type: "Feature",
                geometry: {
                    type: "Point",
                    coordinates: [lng, lat]
                },
                properties: {
                    exampleProperty: propertyValue,
                }
            }
        })

Example for polygon layer

Example with custom icon

const layerStyles = Mapbox.StyleSheet.create({
  icon: {
    iconImage: "{icon}",
    iconAllowOverlap: true,
    iconSize: 0.5,
    iconIgnorePlacement: true
  }
});
const mapboxIcon = props => {
  return (
    <Mapbox.ShapeSource
      shape={makeMapBoxGeoJson(props.datum, props.mapKey, props.name)}
      key={`${props.name}_key_${props.mapKey}`}
      id={`${props.name}_${props.mapKey}`}
      images={getIcon(props.name)}
      onPress={idx => (props.isActive ? props.onSelectId(props.mapKey) : null)}
    >
      <Mapbox.SymbolLayer
        id={`${props.mapKey}_pointlayer`}
        style={[layerStyles.icon, { iconSize: props.iconSize ? props.iconSize : 0.5 }]}
      />
    </Mapbox.ShapeSource>
  );
};

You can add markers dynamically by using this code:

Create marker component:

const Marker = ({ coordinate, image, id }) => {
    return (
      <MapboxGL.MarkerView coordinate={coordinate} id={id}>
      // Add any image or icon or view for marker
        <Image
            source={{ uri: image }}
            style={{width: '100%', height: '100%'}}
            resizeMode="contain"
          />
      </MapboxGL.MarkerView>
    );
 };

Consume it inside MapBoxGL:

<MapboxGL.MapView
      style={{
        // it will help you keep markers inside mapview
        overflow: 'hidden'
      }}>
{markers &&
      markers?.length > 0 &&
         markers.map((marker, index) => (
            <Marker
              coordinate={[marker.longitude, marker.latitude]}
              // id must be a string
              id={`index + 1`}
              image={getIconUrl(index)}
            />
         ))
}
</MapboxGL.MapView>
Related