Using react-native-mapbox-gl/maps, when a SymbolLayer
is dynamically added to a ShapeSource
, it seems it is not shown, or the ShapeSource
is not updated.
Here is the example to reproduce : based on CustomIcon
example, I replaced the code with the code below. To reproduce, just execute the examples, copy-paste the code in place of the existing code in CustomIcon.js
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;
We can see that clicking on the map changes the state, adds a feature, but does not show the feature on the map.
How can we make the ShapeSource
update dynamically ?
The whole discussion about the subject is in here: https://github.com/react-native-mapbox-gl/maps/issues/248
To make it short : I wanted to use dynamics SVGs as SymbolLayer
(so that I can change the colour for instance), but this is not possible : giving SymbolLayer
any child component is not a proper way to do.
We need instead to use Images
in parallel of ShapeSource
and SymbolLayer
, because Images
can be updated dynamically.
Here is a 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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With