Maps: ShapeSource does not update dynamically

Created on 15 Jul 2019  路  20Comments  路  Source: react-native-mapbox-gl/maps

Describe the bug
When a SymbolLayer is dynamically added to a ShapeSource, it seems it is not shown.

To Reproduce
Based on CustomIcon example, I replaced the code with the code below. To reproduce, just copy-paste the code in place of the existing code in CustomIcon example.

import React from 'react';
import { View, Text } from 'react-native';
import MapboxGL from '@react-native-mapbox-gl/maps';

import sheet from '../styles/sheet';

import BaseExamplePropTypes from './common/BaseExamplePropTypes';
import Page from './common/Page';
import Bubble from './common/Bubble';

const styles = {
  icon: {
    iconAllowOverlap: true,
  },
  view: {
    width: 60,
    height: 60,
    borderColor: 'black',
    borderWidth: 1,
    alignItems: 'center',
    justifyContent: 'center'
  },
  text: {
    fontSize: 50
  }
};

const customIcons = ['馃榾', '馃ぃ', '馃構', '馃槩', '馃槵']

class CustomIcon extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      featureCollection: {
        type: 'FeatureCollection',
        features: [{
          type: 'Feature',
          geometry: {
            coordinates: [-73.970895, 40.723279],
            type: 'Point'
          },
          id: 1,
          properties: {
            customIcon: customIcons[0]
          }
        }]
     },
    };

    this.onPress = this.onPress.bind(this);
    this.onSourceLayerPress = this.onSourceLayerPress.bind(this);
  }

  onPress(e) {
    const feature = {
      type: 'Feature',
      geometry: e.geometry,
      id: Date.now(),
      properties: {
        customIcon: customIcons[this.state.featureCollection.features.length]
      }
    };

    this.setState(({ featureCollection }) => ({
      featureCollection: {
        type: 'FeatureCollection',
        features: [
          ...featureCollection.features,
          feature
        ]
      }
    }));
  }

  onSourceLayerPress(e) {
    const feature = e.nativeEvent.payload;
    console.log('You pressed a layer here is your feature', feature); // eslint-disable-line
  }

  render() {
    return (
      <Page {...this.props}>
        <MapboxGL.MapView
          ref={c => (this._map = c)}
          onPress={this.onPress}
          style={sheet.matchParent}
        >
          <MapboxGL.Camera
            zoomLevel={9}
            centerCoordinate={[-73.970895, 40.723279]}
          />

          <MapboxGL.ShapeSource
            id="symbolLocationSource"
            hitbox={{width: 20, height: 20}}
            onPress={this.onSourceLayerPress}
            shape={this.state.featureCollection}
          >
            {this.state.featureCollection.features.map((feature, ind) => (
              <MapboxGL.SymbolLayer
                id={"symbolLocationSymbols" + feature.id}
                key={feature.id}
                filter={['==', 'customIcon', customIcons[ind]]}
                minZoomLevel={1}
                style={styles.icon}
              >
                <View style={styles.view}>
                  <Text style={styles.text}>
                    {feature.properties.customIcon}
                  </Text>
                </View>
              </MapboxGL.SymbolLayer>
            ))}
          </MapboxGL.ShapeSource>
        </MapboxGL.MapView>

        <Bubble>
          <Text>Tap to add an icon</Text>
        </Bubble>
      </Page>
    );
  }
}

export default CustomIcon;

Expected behavior
"Tap to add an icon"

Versions (please complete the following information):

  • Platfrom: [iOS]
  • Device: [iPhone5, iOS 10.3]
  • OS: [iOS 10.3]
  • react-native-mapbox-gl/maps version: 7.0.0-rc3
  • React Native Version 0.59.8
wontfix

Most helpful comment

@mfazekas, @kristfal do you know anything about why this happens?
It makes it kinda impossible to create a map with annotations that are queried from an API and displayed dynamically, which is a rather common use case for annotations and is what I am trying to as well.

All 20 comments

@mfazekas I see that this issue has already appeared quite a few times (#1362 - #1405 - #1335 - #1204 and #1108 in @nitaliano's deprecated repo, and twice (because of me 馃槵) here - this issue, #226 and #227).
I would be very happy to propose a PR solving it, but I am still confuse where is the problem located : is it in JS or in native components ? In Mapview or in ShapeSource ?

Thanks for your help/tips !

I'm having the same issue as @arnaudambro, it would be nice if someone could help us.
Thanks in advance!

@arnaudambro
This app adds a point onclick and then remove it when clicked again.
https://gyazo.com/e68cb2ed6ee0e47246b8588af9f651eb

I guess it's something with indexing, let's take my scenario :
I have six points on the map.
When I delete the for example the third point it will delete it normally.
now I have only 5 points , when trying to delete the 4th point (5th point before deletion) the third point will be deleted (which is the 4th point without deletion).

Also having this issue

I have the same issue, have you solved this please?

I have the issue too and it seems that is has been around for quite some time.
Any working solutions out there that doesn't involve reloading the whole map each time?

@mfazekas, @kristfal do you know anything about why this happens?
It makes it kinda impossible to create a map with annotations that are queried from an API and displayed dynamically, which is a rather common use case for annotations and is what I am trying to as well.

I confirm, It's a big issue with this library right now. I'd like to help.

Confirmed on our side that this is also an issue we are experiencing.

The shapesource loads correctly during the initial load but when the user adds new markers to the map nothing shows up. This seems like a critical issue.

Are there alternative ways of allowing a user to add a marker to the map?

PointAnnotation is apparently going way so that left us with shapesources.

A potential work around is mentioned by @arnaudambro here https://github.com/react-native-mapbox-gl/maps/issues/227#issuecomment-510914789

Any other work around you are aware of? Just trying to allow a user to drop the pin on the map.

@arnaudambro It's working for me if I only use the SymbolLayer:

<MapboxGL.SymbolLayer
  id={id}
  key={id}
  minZoomLevel={1}
  style={{ iconImage: intensityLow, iconAllowOverlap: true, iconSize: 0.85 }}
/>

As soon as I add a React View inside the SymbolLayer it stops working.

I recently upgraded to https://github.com/react-native-mapbox-gl/maps/commit/788782cce2e32482b9b4fa1909a916c44b3920a2, and it seems that the issue has been fixed, even though I can't figure out which commit that did it.
I am only using SymbolLayer instead of View as well.
@arnaudambro Can you and the others try and upgrade to a newer version too, and confirm that you are not facing the issue anymore?

@anfriis I will upgrade ans try to see if something is fixed but I doubt it. Actually, the combo ShapeSource / SymbolLayer has always worked properly, as long as the Images were defined at first and not modified afterwards. the following code for instance works perfectly well, and can add markers on the map quite easily.

The problem comes when we want to insert custom pin, or dynamic pin, like an SVG pin with custom colour or whatever dynamic could be, then it is not working anymore. So with the SVG, the easiest is to add a View as a child of SymbolLayer, but the ShapeSource then is not updated dynamically.

Working code example:

import React from 'react';
import MapboxGL from '@react-native-mapbox-gl/maps';

const myImages = {
  'image-1': 'path/to/image-1',
  'image-2': 'path/to/image-2'
}

const createFeature = ({ 
  showPin, 
  icon = 'image-1', // as long as any added feature has an icon belonging to the static myImages, it works.
  coordinates, 
  id 
}) => ({
  // https://github.com/react-native-mapbox-gl/maps/blob/master/docs/ShapeSource.md -> shapeSource prop
  // https://geojson.org
  // this has a geoJSON shape
  type: 'Feature',
  id,
  properties: {
    showPin,
    icon
  },
  geometry: {
    type: 'Point',
    coordinates,
  }
})

class MyMarkers extends React.Component {


  state = {
    featureCollection: MapboxGL.geoUtils.makeFeatureCollection(),
  }

  componentDidMount() {
    this.updateFeatures()
  }

  componentDidUpdate(prevProps) {
    // update features based on any criteria
    if (conditionOnProps(prevProps, this.props)) this.updateFeatures() 
  }

  updateFeatures() {

    const featureCollection = MapboxGL.geoUtils.makeFeatureCollection()

    for (let feature of this.props.features) {
      MapboxGL.geoUtils.addToFeatureCollection(
        featureCollection,
        createFeature(feature)
      )
    }

    this.setState({ featureCollection });
  }

  onPress = (e) => {
    const feature = e.nativeEvent.payload;
    this.props.doAnythingWithPressedFeature(feature);
  }

  render() {

    return (
      <>
        <MapboxGL.Images images={myImages} />
        <MapboxGL.ShapeSource
          id='markersShape'
          shape={this.props.featureCollection}
          onPress={this.onPress}
        >
          <MapboxGL.SymbolLayer
            id='markersSymbol'
            filter={['==', 'showPin', true]}
            style={{
              iconAllowOverlap: true,
              iconImage: ['get', 'icon'],
            }}
          />
        </MapboxGL.ShapeSource>
      </>
    )
  }
}

export default MyMarkers;

Using the newest version of this has not helped my problem.
To add to the pool of knowledge, I believe the issue is within the ShapeSource, if I add a key that updates when the Shape updates then the icons will properly appear. If I can find out exactly what up in the code, I'll open a PR and see about fixing this.

Hi you all,
Just to tell you that, because of an other issue (#412) I met when a very lot of images where loaded through Images, which was the solution I found to solve the problem here of ShapeSource not updating dynamically, I found out that Images is updating dynamically. So if you have a new image to set as s SymbolLayer, you can add it to the images prop of Images and it should work !
That is a good enough hack for me.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

i'm having this issue. changes in shapesource will re-render the map in debug mode, but not in production

This is a problem still

You must provide initial geojson data and update whenever you want.

I was facing same issue because initial value i was passing {}. so it was not working.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gmaclennan picture gmaclennan  路  3Comments

peterleng picture peterleng  路  4Comments

andrei-tofan picture andrei-tofan  路  5Comments

bartolkaruza picture bartolkaruza  路  3Comments

calypsow777 picture calypsow777  路  5Comments