Maps: ShapeSource and SymbolLayer as PointAnnotation.

Created on 19 Jul 2019  Â·  24Comments  Â·  Source: react-native-mapbox-gl/maps

Problem:
As the docs say PointAnnotation is deprecated and shouldn't be used so I'm trying to switch to ShapeLayer and SymbolLayer. What I'm trying to do is something like this:
facebook_blog_image_2
And the number should change dynamically, according to props.
Any tips? I managed to make the chat icon appear but I have no idea how to add the number.
What I have so far:

const feature = {
type: 'Feature',
id: marker.id,
      geometry: {
        type: 'Point',
        coordinates: [marker.lat, marker.lng],
      },
};
return (
<MapboxGL.MapView style={sheet.matchParent}>
     <MapboxGL.Camera
          zoomLevel={17}
          centerCoordinate={[loc.lat, loc.lng]}
        />
     <MapboxGL.ShapeSource
         id="exampleShapeSource"
         shape={feature}
       >
        <MapboxGL.SymbolLayer id={markerOne + 1} style={{ iconImage: image, iconSize: 1 }} />
    </MapboxGL.ShapeSource>
</MapboxGL.MapView>);

Thanks in advance.

Most helpful comment

Here is full example we can include to repository.

import React, { Component } from "react";
import { StyleSheet, View, Text, Image } from "react-native";
import MapboxGL from "@react-native-mapbox-gl/maps";
import sheet from '../examples/styles/sheet';
import exampleIcon from '../examples/assets/example.png';

const point = {
  id: "1",
  lat: 40.723279,
  lng: -73.970895
}

const featureCollection = {
  "type": "FeatureCollection",
  "features": [
    {
      type: "Feature",
      id: point.id,
      properties: {
        id: point.id
      },
      geometry: {
        type: "Point",
        coordinates: [point.lng, point.lat],
      },
    }
  ]
}

export default class App extends Component {

  state = {
    count: 0
  }

  onPress = async () => {
    this.setState((prevState) => ({
      count: prevState.count + 1
    }));
  }

  render() {
    return (
      <MapboxGL.MapView
        style={sheet.matchParent}
      >
        <MapboxGL.Camera
          zoomLevel={10}
          centerCoordinate={[point.lng, point.lat]}
        />
        <MapboxGL.ShapeSource
          id="symbolLocationSource"
          hitbox={{ width: 30, height: 30 }}
          onPress={this.onPress}
          shape={featureCollection}
        >
          <MapboxGL.CircleLayer
            id="pointCircles"
            style={{
              circleStrokeColor: "#0000FF",
              circleStrokeWidth: 2,
              circleRadius: 30,
              circleColor: "#FFFFFF"
            }}
          />
          <MapboxGL.SymbolLayer
            id="teaCup"
            aboveLayerID="pointCircles"
            style={{
              iconImage: exampleIcon,
              iconSize: 1
            }}
          />
          <MapboxGL.CircleLayer
            id="notificationCircle"
            style={{
              circleColor: "#F00",
              circleRadius: 14,
              circleTranslate: [22, -22]
            }}
          />
          <MapboxGL.SymbolLayer
            id="notificationCount"
            style={{
              iconOptional: true,
              textIgnorePlacement: true,
              textField: this.state.count.toString(),
              textSize: 15,
              textMaxWidth: 50,
              textColor: '#FFF',
              textAnchor: 'center',
              textTranslate: [22, -22],
              textAllowOverlap: true
            }}
          />
        </MapboxGL.ShapeSource>
      </MapboxGL.MapView>
    );
  }
}

Usage looks like this - count is updated on icon press

SymbolLayerUpdates

All 24 comments

@tomasamado97 note that you can add a <View /> as a <SymbolLayer> child. Maybe that's how you would put the number.

Cool, its working for IOS but for android it doesn't even show.
Screenshot_20190722_133546
IOS:
Screen Shot 2019-07-22 at 1 38 25 PM

Plus I want to dynamically change the purple picture to a green one, and its always staying on the same one.

@arnaudambro did you know how to implement SymbolLayers ?

Is this the way?

<MapboxGL.ShapeSource id="exampleShapeSource" shape={featurelist} cluster={false} > <MapboxGL.SymbolLayer id="exampleIconName" style={layerStyles.iconBlue} /> </MapboxGL.ShapeSource>

The points are not shown on my map in Android, anyone have a working example ?

@arnaudambro
That is the example i tried to use, but somehow the icon does not show on the android mapview.

can you show your code ?

I can show you my code. I'm having the same issue as @Friis1978. Plus I'm facing the issue that the markers don't update when I change props.
Image is supposed to change, making the marker change.
I also tried adding a key to the shapeSource to make it update when the key changes, to no result.
Code:
Screen Shot 2019-07-23 at 9 29 56 AM

In the shape prop of ShapeSource, you are passing a feature whereas you should be passing a feature collection, check again the ShapeSourceIcon example

Still having the same issue, it shows on IOS but it doesn't on Android

I was able to make the markers show and change dynamically by using styles on SymbolLayer but now the view with the amount of matches disappeared.

@tomasamado97 can you show your new code with the symbollayer styles also?

I just added:
const markerStyle= isMarkerOne ? { iconImage: imageOne, iconSize: 0.55 } ? { iconImage: imageTwo, iconSize: 1 };

And then add it to the SymbolLayer like:

      <Mapbox.SymbolLayer
          id={insideHoneypot.id + 2}
          styles={markerStyle}
       />

But adding this makes the <View /> inside the Symbol not work, so I lose the numbers for the matches.

Which version of this library deprecated PointAnnotation

Maybe not related but if use something like const featureCollection = MapboxGL.geoUtils.addToFeatureCollection(this.state.featureCollection, feature) map dont re-render

U need clone items like:

const featureCollection = _.cloneDeep(MapboxGL.geoUtils.addToFeatureCollection(this.state.featureCollection, feature))

Maybe u have to clone ur item

@arnaudambro @tomasamado97

Are there any docs or examples for adding custom component children (Views) to SymbolLayers? This is a revelation for me but I can't get it working (maybe because of the same Android issue..)

@dancherb here is an example of use. This is from my code, and it works perfectly.

`renderAllAnnotations() {
const { userlist } = this.props;

return (
  <MapboxGL.ShapeSource
    id="symbolLocationSource"
    hitbox={{ width: 20, height: 20 }}
    onPress={this.onActiveClick}
    shape={userlist}
  >
    <MapboxGL.CircleLayer
      id="pointCircles"
      style={{ circleStrokeColor: this.props.colors.btnColor, circleStrokeWidth: 2, circleRadius: 20, circleColor: this.props.colors.white }}
    />
    <MapboxGL.SymbolLayer
      id="pointSymbols"
      aboveLayerID="pointCircles"
      belowLayerID="homeSymbols"
      filter={['==', 'icon', 'point']}
      style={layerStyles.iconBlue}
    />

    <MapboxGL.SymbolLayer
      id="homeSymbols"
      aboveLayerID="pointCircles"
      filter={['==', 'icon', 'home']}
      style={layerStyles.iconHome}
    />
  </MapboxGL.ShapeSource>
);

}`

Perhaps its not what you are looking for afterall, I can see now, that it was children of symbollayer, and about that I dont know, just wanted to give an example of use.

Nice! Would really appreciate this as an example in the project if you’re
up for it.

On Fri, 20 Sep 2019 at 18:05, Friis1978 notifications@github.com wrote:

@dancherb https://github.com/dancherb here is an example of use. This
is from my code, and it works perfectly.

`renderAllAnnotations() {
const { userlist } = this.props;

return (
id="symbolLocationSource"
hitbox={{ width: 20, height: 20 }}
onPress={this.onActiveClick}
shape={userlist}
>
id="pointCircles"
style={{ circleStrokeColor: this.props.colors.btnColor, circleStrokeWidth: 2, circleRadius: 20, circleColor: this.props.colors.white }}
/>
id="pointSymbols"
aboveLayerID="pointCircles"
belowLayerID="homeSymbols"
filter={['==', 'icon', 'point']}
style={layerStyles.iconBlue}
/>

<MapboxGL.SymbolLayer
  id="homeSymbols"
  aboveLayerID="pointCircles"
  filter={['==', 'icon', 'home']}
  style={layerStyles.iconHome}
/>


);

}`

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/react-native-mapbox-gl/maps/issues/266?email_source=notifications&email_token=ABLAPW53QYIXI6SISONCBM3QKTYDVA5CNFSM4IFIOLE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7HFHBA#issuecomment-533615492,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABLAPW3P6M36RXOSAOMUPYLQKTYDVANCNFSM4IFIOLEQ
.

@dancherb here is an example of use. This is from my code, and it works perfectly.

}`

Perhaps its not what you are looking for afterall, I can see now, that it was children of symbollayer, and about that I dont know, just wanted to give an example of use.

Ah this is standard practice for symbol layers AFAIK - I was looking to avoid a couple issues (with regards to overlapping, custom text etc) by having each symbol layer made up of more complex shapes (instead of only a circle/icon/text) by feeding children into them, like a couple others have suggested in their code. Thanks for the reply nonetheless :)

@kristfal you're welcome to use the code in the project, but perhaps an example of the layerstyle, and shape should be there too.

`
// Png images could be any pictures, without background.
import blueLocation from '../assets/location-marker-blue.png';
import blueHome from '../assets/home_blue.png';

/*
feature example to be used in the userlist

const feature = {
type: 'Feature',
icon: 'home',
id: marker.id,
geometry: {
type: 'Point',
coordinates: [marker.lat, marker.lng],
},
};

*/

renderAllAnnotations() {
const { userlist } = this.props;

return (
id="symbolLocationSource"
hitbox={{ width: 20, height: 20 }}
onPress={this.onActiveClick}
shape={userlist}
>
id="pointCircles"
style={{ circleStrokeColor: this.props.colors.btnColor, circleStrokeWidth: 2, circleRadius: 20, circleColor: this.props.colors.white }}
/>
id="pointSymbols"
aboveLayerID="pointCircles"
belowLayerID="homeSymbols"
filter={['==', 'icon', 'point']}
style={layerStyles.iconBlue}
/>

<MapboxGL.SymbolLayer
  id="homeSymbols"
  aboveLayerID="pointCircles"
  filter={['==', 'icon', 'home']}
  style={layerStyles.iconHome}
/>


);

const layerStyles = {
iconHome: {
iconAllowOverlap: true,
iconIgnorePlacement: true,
iconSize: 0.4,
iconImage: blueHome,
},
iconBlue: {
iconAllowOverlap: true,
iconIgnorePlacement: true,
iconSize: 0.4,
iconImage: blueLocation,
},
};`

Here is full example we can include to repository.

import React, { Component } from "react";
import { StyleSheet, View, Text, Image } from "react-native";
import MapboxGL from "@react-native-mapbox-gl/maps";
import sheet from '../examples/styles/sheet';
import exampleIcon from '../examples/assets/example.png';

const point = {
  id: "1",
  lat: 40.723279,
  lng: -73.970895
}

const featureCollection = {
  "type": "FeatureCollection",
  "features": [
    {
      type: "Feature",
      id: point.id,
      properties: {
        id: point.id
      },
      geometry: {
        type: "Point",
        coordinates: [point.lng, point.lat],
      },
    }
  ]
}

export default class App extends Component {

  state = {
    count: 0
  }

  onPress = async () => {
    this.setState((prevState) => ({
      count: prevState.count + 1
    }));
  }

  render() {
    return (
      <MapboxGL.MapView
        style={sheet.matchParent}
      >
        <MapboxGL.Camera
          zoomLevel={10}
          centerCoordinate={[point.lng, point.lat]}
        />
        <MapboxGL.ShapeSource
          id="symbolLocationSource"
          hitbox={{ width: 30, height: 30 }}
          onPress={this.onPress}
          shape={featureCollection}
        >
          <MapboxGL.CircleLayer
            id="pointCircles"
            style={{
              circleStrokeColor: "#0000FF",
              circleStrokeWidth: 2,
              circleRadius: 30,
              circleColor: "#FFFFFF"
            }}
          />
          <MapboxGL.SymbolLayer
            id="teaCup"
            aboveLayerID="pointCircles"
            style={{
              iconImage: exampleIcon,
              iconSize: 1
            }}
          />
          <MapboxGL.CircleLayer
            id="notificationCircle"
            style={{
              circleColor: "#F00",
              circleRadius: 14,
              circleTranslate: [22, -22]
            }}
          />
          <MapboxGL.SymbolLayer
            id="notificationCount"
            style={{
              iconOptional: true,
              textIgnorePlacement: true,
              textField: this.state.count.toString(),
              textSize: 15,
              textMaxWidth: 50,
              textColor: '#FFF',
              textAnchor: 'center',
              textTranslate: [22, -22],
              textAllowOverlap: true
            }}
          />
        </MapboxGL.ShapeSource>
      </MapboxGL.MapView>
    );
  }
}

Usage looks like this - count is updated on icon press

SymbolLayerUpdates

So do you have to render one "notificationCount"-symbolLayer for each point? if you have multiple points on the map that have different notification counters.

@mxhryvo I think you need a feature collection for that, and you set a feature for each of the points of the map you want to render. I think there is an example on the docs of the library.

Apart from that I think this has been resolved so I'm closing the issue. If you need any help, feel free to ask me.

So do you have to render one "notificationCount"-symbolLayer for each point? if you have multiple points on the map that have different notification counters.

Hi, @mxhryvo, if you want to dynamically display data on symbolLayers, you have to do it like this :

<MapboxGL.SymbolLayer
            id="notificationCount"
            style={{
              iconOptional: true,
              textIgnorePlacement: true,
              textField: '{notificationCount}',
              textSize: 15,
              textMaxWidth: 50,
              textColor: '#FFF',
              textAnchor: 'center',
              textTranslate: [22, -22],
              textAllowOverlap: true
            }}
          />

where notificationCount is a property in your features like this :
{ type: 'Feature', id: '11', properties: { icon: 'exempleIcon', notificationCount : dynamicValue }, geometry: { type: 'Point', coordinates: [-117.205908, 52.180843], }, }

@drik I understand the SymbolLayer textField: {notificationCount} will render the dynamicValue value of the feature:properties:notificationCount.
But is there a reason why, or does it matter that the id for the SymbolLayer is notificationCount also ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mustafaskyer picture mustafaskyer  Â·  3Comments

RichardLindhout picture RichardLindhout  Â·  4Comments

SethArchambault picture SethArchambault  Â·  3Comments

lukemcgregor picture lukemcgregor  Â·  5Comments

magnusburton picture magnusburton  Â·  3Comments