Skip to content

SymbolLayer

This layer is used to load point information and allows setting properties such as icon and text. It also supports point clustering and offers rich functionality. Generally, this layer should be used for adding points to the map.

Non-Clustered Example

Example Source Code
tsx
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import { MbMap, MbSymbolLayer, MbTiandituLayer } from '@mapbox-react/core'
import type { SymbolLayerData } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.194322, 39.925238])
  const [zoom, setZoom] = useState(8)

  const [symbolDataSource, setSymbolDataSource] = useState<SymbolLayerData>([])

  const getRandomArbitrary = (min: number, max: number) => {
    return Math.random() * (max - min) + min
  }
  const loadSymbolData = () => {
    const data: SymbolLayerData = []
    for (let i = 0; i < 2000; i++) {
      data.push({
        coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
        properties: {
          label: `测试-${i}`,
          icon: 'airport-15',
        },
      })
    }
    setSymbolDataSource(data)
  }

  useEffect(() => {
    loadSymbolData()
  }, [])

  const clickLayerHandler = (payload: any) => {
    console.log('clickLayerHandler', payload)
  }
  const leaveLayerHandler = () => { }

  return (
    <div className="map-wrapper">
      <MbMap
        center={mapCenter}
        zoom={zoom}
        glyphs="https://mapbox-web.github.io/mapbox-react/fonts/{fontstack}/{range}.pbf"
        sprite="https://mapbox-web.github.io/mapbox-react/sprites/sprite"
      >
        <MbTiandituLayer types={['vec']} />
        <MbSymbolLayer
          data={symbolDataSource}
          textField="label"
          textFont={['Open Sans Regular']}
          iconImageField="icon"
          textHaloColor="#9e9e9e"
          textHaloWidth={1}
          textOffset={[0, -1]}
          cluster={false}
          onMouseMove={clickLayerHandler}
          onMouseLeave={leaveLayerHandler}
        />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))

Clustered Example

Example Source Code
tsx
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import { MbMap, MbSymbolLayer, MbTiandituLayer } from '@mapbox-react/core'
import type { SymbolLayerData } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.194322, 39.925238])
  const [zoom, setZoom] = useState(4.5)

  const [symbolDataSource, setSymbolDataSource] = useState<SymbolLayerData>([])

  const getRandomArbitrary = (min: number, max: number) => {
    return Math.random() * (max - min) + min
  }
  const loadSymbolData2 = () => {
    const data: SymbolLayerData = []
    for (let i = 0; i < 2000; i++) {
      data.push({
        coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
        properties: {
          label: `测试-${i}`,
          icon: 'airport-15',
        },
      })
    }
    for (let i = 0; i < 13; i++) {
      data.push({
        coordinates: [116.678, 31.456],
        properties: {
          label: `same-${i}`,
          icon: 'airport-15',
        },
      })
    }
    setSymbolDataSource(data)
  }

  useEffect(() => {
    loadSymbolData2()
  }, [])

  return (
    <div className="map-wrapper">
      <MbMap
        center={mapCenter}
        zoom={zoom}
        glyphs="https://mapbox-web.github.io/mapbox-react/fonts/{fontstack}/{range}.pbf"
        sprite="https://mapbox-web.github.io/mapbox-react/sprites/sprite"
      >
        <MbTiandituLayer types={['vec']} />
        <MbSymbolLayer
          data={symbolDataSource}
          textField="label"
          textFont={['Open Sans Regular']}
          iconImageField="icon"
          textOffset={[0, -1]}
          cluster={true}
          spiderify={true}
        />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))

Point Displacement Example (Spiderfy)

Zoom in on the map. After the clustered points can no longer be separated by zooming, you can click on the cluster point, and the clustered points will spread out in a certain shape.

Preview

Building Layer

Example Source Code
tsx
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import {
  MbImageLoader,
  MbMap,
  MbSymbolLayer,
  MbTiandituLayer,
} from '@mapbox-react/core'
import type { SymbolLayerData } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.678, 31.586])
  const [zoom, setZoom] = useState(8)

  const [symbolDataSource, setSymbolDataSource] = useState<SymbolLayerData>([])
  const [symbolDataSource2, setSymbolDataSource2] = useState<SymbolLayerData>(
    []
  )

  const images = [
    {
      name: 'cluster-icon',
      type: 'link',
      url: 'https://mapbox-web.github.io/mapbox-react/images/common_toget.png',
    },
    {
      name: 'cluster-icon2',
      type: 'link',
      url: 'https://mapbox-web.github.io/mapbox-react/images/eme_team_soc_toget.png',
    },
  ]

  const clusterIcons = [
    {
      textColor: 'white',
      textSize: 12,
      iconName: 'cluster-icon',
    },
  ]
  const clusterIcons2 = [
    {
      textColor: 'white',
      textSize: 16,
      iconName: 'cluster-icon2',
    },
  ]

  const loadSymbolData3 = () => {
    const data: SymbolLayerData = [],
      data2: SymbolLayerData = []
    for (let i = 0; i < 13; i++) {
      data.push({
        coordinates: [116.678, 31.456],
        properties: {
          label: `same-${i}`,
          icon: 'airport-15',
        },
      })
      data2.push({
        coordinates: [116.788, 31.956],
        properties: {
          label: `same2-${i}`,
          icon: 'airport-15',
        },
      })
    }
    for (let i = 0; i < 5; i++) {
      data.push({
        coordinates: [116.688, 31.456],
        properties: {
          label: `same-2-${i}`,
          icon: 'airport-15',
        },
      })
      data2.push({
        coordinates: [116.788, 31.956],
        properties: {
          label: `same-2-${i}`,
          icon: 'airport-15',
        },
      })
    }
    setSymbolDataSource(data)
    setSymbolDataSource2(data2)
  }

  useEffect(() => {
    loadSymbolData3()
  }, [])

  return (
    <div className="map-wrapper">
      <MbMap
        center={mapCenter}
        zoom={zoom}
        glyphs="https://mapbox-web.github.io/mapbox-react/fonts/{fontstack}/{range}.pbf"
        sprite="https://mapbox-web.github.io/mapbox-react/sprites/sprite"
      >
        <MbImageLoader images={images} />
        <MbTiandituLayer types={['vec']} />
        <MbSymbolLayer
          data={symbolDataSource}
          textField="label"
          textFont={['Open Sans Regular']}
          iconImageField="icon"
          clusterIcons={clusterIcons}
          clusterIconSize={0.5}
          clusterIconOffset={[1, 5]}
          cluster={true}
          spiderify={true}
        />
        <MbSymbolLayer
          data={symbolDataSource2}
          textField="label"
          textFont={['Open Sans Regular']}
          iconImageField="icon"
          clusterIcons={clusterIcons2}
          clusterIconSize={0.5}
          clusterIconOffset={[1, 45]}
          cluster={true}
          spiderify={true}
        />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))

Animation Example

Example Source Code
tsx
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import * as turf from '@turf/turf'
import {
  MbImageLoader,
  MbMap,
  MbPolylineLayer,
  MbSymbolLayer,
  MbTiandituLayer,
} from '@mapbox-react/core'
import type { SymbolLayerData } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([87.5390625, 11.178401873711785])
  const [zoom, setZoom] = useState(1)

  const images = [
    {
      name: 'cluster-icon',
      type: 'link',
      url: 'https://mapbox-web.github.io/mapbox-react/images/common_toget.png',
    },
    {
      name: 'cluster-icon2',
      type: 'link',
      url: 'https://mapbox-web.github.io/mapbox-react/images/eme_team_soc_toget.png',
    },
  ]
  const polylineCoordinates = [
    {
      coordinates: [
        [137.4609375, 39.639537564366684],
        [136.7578125, 49.38237278700955],
        [126.5625, 54.77534585936447],
        [103.35937499999999, 53.9560855309879],
        [83.3203125, 47.040182144806664],
        [66.796875, 34.59704151614417],
        [73.47656249999999, 21.94304553343818],
        [87.5390625, 11.178401873711785],
        [105.1171875, 8.754794702435618],
        [115.6640625, 13.581920900545844],
        [114.9609375, 25.48295117535531],
        [93.8671875, 25.799891182088334],
        [85.4296875, 31.653381399664],
        [91.0546875, 39.095962936305476],
        [104.765625, 41.50857729743935],
        [114.9609375, 41.50857729743935],
        [121.28906250000001, 37.71859032558816],
        [127.265625, 32.84267363195431],
        [130.78125, 28.92163128242129],
      ],
    },
    {
      coordinates: [
        [47.8125, -24.846565348219734],
        [183.1640625, -19.642587534013032],
        [176.48437499999997, 70.02058730174062],
        [33.046875, 67.60922060496382],
        [33.046875, -22.91792293614603],
        [31.9921875, -26.11598592533351],
      ],
    },
  ]

  let counter = 0
  const steps = 18
  const start = () => {
    counter = 0
    animate()
  }
  const animate = () => {
    const start =
      polylineCoordinates[0].coordinates[
      counter >= steps ? counter - 1 : counter
      ]
    const end =
      polylineCoordinates[0].coordinates[
      counter >= steps ? counter : counter + 1
      ]
    if (!start || !end || counter > steps) {
      counter = 0
      return
    }
    // Update point geometry to a new position based on counter denoting
    // the index to access the arc
    // symbolDataSource.value[0].coordinates = polylineCoordinates[0].coordinates[counter]

    // Calculate the bearing to ensure the icon is rotated to match the route arc
    // The bearing is calculated between the current point and the next point, except
    // at the end of the arc, which uses the previous point and the current point
    // symbolDataSource.value[0].properties.bearing = turf.bearing(
    //   turf.point(start),
    //   turf.point(end),
    // )

    setSymbolDataSource([
      {
        ...symbolDataSource[0],
        coordinates: polylineCoordinates[0].coordinates[counter],
        properties: {
          ...symbolDataSource[0].properties,
          bearing: turf.bearing(turf.point(start), turf.point(end)),
        },
      },
    ])

    // Request the next frame of animation as long as the end has not been reached
    if (counter < steps) {
      // requestAnimationFrame(this.animate);
      window.setTimeout(animate, 1500)
    }

    counter++
  }

  const [symbolDataSource, setSymbolDataSource] = useState<SymbolLayerData>([])

  const loadSymbolData4 = () => {
    setSymbolDataSource([
      {
        coordinates: [116.28, 39.91],
        properties: {
          label: ``,
          icon: 'cluster-icon',
          iconSize: 0.5,
          bearing: 0,
        },
      },
    ])
  }

  useEffect(() => {
    loadSymbolData4()
  }, [])

  return (
    <div className="map-wrapper">
      <MbMap
        center={mapCenter}
        zoom={zoom}
        glyphs="https://mapbox-web.github.io/mapbox-react/fonts/{fontstack}/{range}.pbf"
        sprite="https://mapbox-web.github.io/mapbox-react/sprites/sprite"
      >
        <div className="action-bar">
          <button className="primary" onClick={start}>
            重新开始
          </button>
        </div>
        <MbImageLoader images={images} />
        <MbTiandituLayer types={['vec']} />

        <MbPolylineLayer data={polylineCoordinates} width={3} color="blue" />
        <MbSymbolLayer
          data={symbolDataSource}
          iconImageField="icon"
          iconAnchor="center"
          iconSize={0.5}
          iconRotationAlignment="map"
          iconAllowOverlap={true}
          iconRotate={['get', 'bearing']}
          iconIgnorePlacement={true}
        />
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))
Example Source Code
tsx
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import {
  MbMap,
  MbMapPopup,
  MbSymbolLayer,
  MbTiandituLayer,
} from '@mapbox-react/core'
import type { SymbolLayerData } from '@mapbox-react/core'

const App = () => {
  const [mapCenter] = useState([116.194322, 39.925238])
  const [zoom, setZoom] = useState(8)

  const [symbolDataSource, setSymbolDataSource] = useState<SymbolLayerData>([])

  const getRandomArbitrary = (min: number, max: number) => {
    return Math.random() * (max - min) + min
  }
  const loadSymbolData = () => {
    const data: SymbolLayerData = []
    for (let i = 0; i < 2000; i++) {
      data.push({
        coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
        properties: {
          id: String(Math.random()),
          label: `Airport-${i}`,
          icon: 'airport-15',
        },
      })
    }
    setSymbolDataSource(data)
  }

  useEffect(() => {
    loadSymbolData()
  }, [])

  return (
    <div className="map-wrapper">
      <MbMap
        center={mapCenter}
        zoom={zoom}
        glyphs="https://mapbox-web.github.io/mapbox-react/fonts/{fontstack}/{range}.pbf"
        sprite="https://mapbox-web.github.io/mapbox-react/sprites/sprite"
      >
        <MbTiandituLayer types={['vec']} />
        <MbSymbolLayer
          data={symbolDataSource}
          textField="label"
          textFont={['Open Sans Regular']}
          iconImageField="icon"
          textHaloColor="#9e9e9e"
          textHaloWidth={1}
          textOffset={[0, -1]}
        />
        {symbolDataSource.map((s) => (
          <MbMapPopup
            key={s.properties?.id}
            coordinates={s.coordinates}
            offset={[0, -10]}
            showTip={false}
            anchor="bottom"
          >
            <div
              style={{
                padding: '4px',
                backgroundColor: 'white',
                boxShadow: '10px 5px 10px grey',
              }}
            >
              {`${s.properties?.label}`}
            </div>
          </MbMapPopup>
        ))}
      </MbMap>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#root'))

API

PROPS

名称描述类型默认值
idLayer IDstring-
clusterWhether to enable clusteringbooleanfalse
clusterIconsConfiguration information for cluster icons. See example for detailsIndexAny[]见示例说明
clusterMaxZoomThe maximum zoom level at which clustering is enablednumber14
clusterMinPointsThe minimum number of points required for clusteringnumber2
clusterRadiusClustering radius in pixelsnumber50
clusterIconSizeIcon scaling factornumber1
clusterIconTranslateIcon anchor translation. Positive values shift right/downnumber[][0,0]
clusterIconOffsetDistance between the icon and its anchor. Positive values shift right/downnumber[][0,0]
clusterIconAnchorIcon anchor positionstringcenter
dataData for the vector layer. Each object in the array must contain the required coordinates property and the optional properties property. Each object represents a point/line/polygonSymbolLayerData-
geoJsonDataSourceGeoJSON data source. Can be a GeoJSON data link or a GeoJSON data object. Specification Link. If this property is set, the data property is ignored.string / GeoJSONSource-
iconAllowOverlapWhether to allow icon overlapbooleanfalse
iconAnchorIcon anchor positionstring("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
iconColorIcon color. Only effective for SDF iconsstring#000000
iconImageIcon resource. Pre-loaded image namestring / AnyArr-
iconImageFieldIcon image field name. Will be read from the dataSource properties, overriding the iconImage propertystring-
iconOffsetDistance between the icon and its anchor. Positive values shift right/downnumber[][0,0]
iconOpacityIcon opacitynumber1
iconOptionalIf true, the icon will only be displayed if it doesn't collide with other icons, but the text does not collide with other textbooleanfalse
iconRotateIcon rotation anglenumber0
iconRotationAlignment-stringauto
iconIgnorePlacement-booleanfalse
iconSizeIcon scaling factornumber1
iconTranslateIcon anchor translation. Positive values shift right/downnumber[][0,0]
maxzoomMaximum zoom levelnumber22
minzoomMinimum zoom levelnumber0
pickableWhether the layer responds to pick events. If false, the component will not emit mouse-related eventsbooleantrue
showWhether to show the layerbooleantrue
sourceIdID of the layer's source. If this ID is set, the geoJsonDataSource and data properties are ignoredstring-
sourceLayerNameLayer name in the source data. This property is effective when sourceId is setstring-
textText value. Only effective if textFiled is not specifiedstring-
textAllowOverlapWhether to allow text overlapbooleanfalse
textAnchorText anchor positionstring("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
textColorText colorstring#000000
textFieldText field value/key. Will be taken from the dataSource propertiesstring-
textFontText fontstring[]["Open Sans SemiBold"]
textHaloBlurText halo blur effect in pixelsnumber0
textHaloColorText halo colorstringrgba(0, 0, 0, 0)
textHaloWidthText halo width in pixelsnumber0
textLetterSpacingText letter spacing in em unitsnumber0
textLineHeightLine height in em unitsnumber1.2
textMaxWidthMaximum text width in em unitsnumber10
textOffsetDistance between the text and its anchor. Positive values shift right/down in pixelsnumber[][0,0]
textOpacityText opacitynumber1
textOptionalIf true, the icon will be displayed only if the text collides with other text but the icon does not collide with other iconsbooleanfalse
textSizeText font size in pixelsnumber16
textTranslateText anchor translation. Positive values shift right/down in pixelsnumber[][0,0]
textPaddingText paddingnumber2
spiderifyWhether points with the same latitude and longitude can be spread out (spiderified)booleanfalse
maxLeafesTospiderifyMaximum number of points to spiderifynumber50
distanceBetweenSpiderPointsDistance between spiderified pointsnumber50
spiderLegColorColor of the lines connecting the spiderified pointsstring#B6CBED
spiderLegWidthWidth of the lines connecting the spiderified pointsnumber3
comparePrecisionThe number of decimal places used when comparing clustered points for equality. Decimal places beyond comparePrecision will be truncatednumber5
clusterSpiralPointsThe shape of the cluster expansion. If greater than this value, the expansion is spiral; if less than or equal to this value, the expansion is circularnumber10

TIP

The clusterIcons property allows you to configure icons for clusters. You can set the iconName property to use images loaded by the ImageLoader. Alternatively, you can set the functionName and functionProps properties to use built-in custom graphics within the ImageLoader. Refer to the ImageLoader documentation for details on the built-in functions. It also supports using custom functions for functionName; refer to the functionName method within the ImageLoader for implementation details.

Default value:

js
[
  {
    count: 50,
    textColor: "black",
    textSize: 12,
    functionName: "drawDynamicCircle",
    functionProps: {
      color: "#68B8EE",
      radius: 50,
    },
  },
  {
    count: 100,
    textColor: "black",
    textSize: 14,
    functionName: "drawDynamicCircle",
    functionProps: {
      color: "#B1E345",
      radius: 75,
    },
  },
  {
    count: 200,
    textColor: "white",
    textSize: 18,
    functionName: "drawDynamicCircle",
    functionProps: {
      color: "#F7DC00",
      radius: 100,
    },
  },
  {
    count: 500,
    textColor: "white",
    textSize: 20,
    functionName: "drawDynamicCircle",
    functionProps: {
      color: "#FA9800",
      radius: 125,
    },
  },
  {
    textColor: "white",
    textSize: 22,
    functionName: "drawDynamicCircle",
    functionProps: {
      radius: 150,
      color: "#F34A4A",
    },
  },
]

EVENTS

NameDescriptionParameters
onCreatedMap initialization completed event-
onClickLayer click eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
onClusterClickClick event for non-drillable cluster points in layerobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), cluster data, and originalEvent
onMouseMoveMouse move eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
onMouseEnterMouse enter eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
onMouseLeaveMouse leave element eventMapMouseEvent

METHODS

NameDescriptionDefinition
exportToGeoJsonTranslate the layer's data to GeoJSON formatted text. If the layer's data source comes from a source component or a URL, output null.()=> object | null