Skip to content

符号图层(SymbolLayer)

用来加载点位信息,可设置icon以及text等属性,另还可实现点聚合功能,功能丰富。一般情况下,地图打点应该使用此图层。

非聚合示例

示例源码
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'))

聚合示例

示例源码
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'))

点避让示例

放大地图,在聚合点不能在散开以后,可以点击聚合点,聚合的点将以某个形状散开。

效果图

Building Layer

示例源码
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'))

动画示例

示例源码
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'))

Popup示例

示例源码
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

名称描述类型默认值
id图层idstring-
cluster是否聚合booleanfalse
clusterIcons聚合图层图标配置信息IndexAny[]见示例说明
clusterMaxZoom聚合效果可用的最大缩放级别number14
clusterMinPoints聚合效果最小聚合点数number2
clusterRadius聚合半径,单位像素number50
clusterIconSizeIcon放大倍数number1
clusterIconTranslateIcon锚点平移量,正方向为右/下number[][0,0]
clusterIconOffsetIcon与其锚点的距离 正向为右/下number[][0,0]
clusterIconAnchorIcon锚点位置stringcenter
data矢量图层的数据 数组中每个对象需包含必需的coordinates属性以及可选的properties属性 每一个对象代表一个点/一条线/一个多边形SymbolLayerData-
geoJsonDataSourcegeojson数据源,可设为geojson数据链接或geojson数据对象;规范链接, 若设置了本属性,则data属性失效string / GeoJSONSource-
iconAllowOverlap是否允许重叠booleanfalse
iconAnchor锚点位置string("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
iconColor图标颜色,仅在sdf icons生效string#000000
iconImageIcon资源,预加载好的图片名string / AnyArr-
icoImageFieldIcon图片字段名,将从dataSource properties中读取 覆盖iconImage属性string-
iconOffsetIcon与其锚点的距离 正向为右/下number[][0,0]
iconOpacityIcon透明度number1
iconOptional若为true,则icon与其他icon产生碰撞但Text未碰撞时,将只显示textbooleanfalse
iconRotateIcon旋转角度number0
iconRotationAlignment-stringauto
iconIgnorePlacement-booleanfalse
iconSize图标放大倍数number1
iconTranslate锚点平移量,正方向为右/下number[][0,0]
maxzoom最大缩放级别number22
minzoom最小缩放级别number0
pickable图层是否响应拾取事件,若为false,则组件不会emit鼠标相关事件booleantrue
show是否显示booleantrue
sourceId图层Source的id,若设置了本ID,则geoJsonDataSource与data属性都会失效string-
sourceLayerName源数据中图层名称 设置sourceId时,此属性生效string-
text文本值,仅在textFiled未指定时生效string-
textAllowOverlap是否允许重叠booleanfalse
textAnchor锚定位置string("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
textColor字体颜色string#000000
textField文本字段值/键值,将从dataSource properties中取值string-
textCont文字字体 Open Sans SemiBoldarray["Open Sans SemiBold"]
textHaloBlur文本边框炫光效果,以像素为单位number0
textHaloColor文本边框颜色stringrgba(0, 0, 0, 0)
textHaloWidth文本边框宽度,像素为单位number0
textLetterSpacing文字间距 em为单位number0
textLineHeightem为单位number1.2
textMaxWidthem为单位number10
textOffsettext与其锚点的距离 正向为右/下 pxnumber[][0,0]
textOpacity字体透明度number1
textOptional若为true,则text与其他text产生碰撞但icon未碰撞时,将只显示iconbooleanfalse
textSize文本字号大小,单位pxnumber16
textTranslate锚点平移量,正方向为右/下 pxnumber[][0,0]
textPadding文字间距number2
spiderify具有相同经纬度点是否可以展开booleanfalse
maxLeafesTospiderify最大展开数量number50
distanceBetweenSpiderPoints展开点间距number50
spiderLegColor展开点线颜色string#B6CBED
spiderLegWidth展开点线宽度number3
comparePrecision聚合点比较相等时的小数位数,大于comparePrecision位的小数会被截掉number5
clusterSpiralPoints聚合点形展开形式,大于是螺旋形,小于等于是圆形number10

TIP

clusterIcons 可以设置 iconName 使用 ImageLoader 中加载的图片。也可以设置functionNamefunctionProps 属性,使用ImageLoader中内置自定义的图形,内置函数可参考ImageLoader中详细介绍。也支持functionName为自定义函数,参考ImageLoader中的functionName方法。

默认值为:

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

名称描述参数
onCreated地图初始化完成事件-
onClick图层单击事件object — 包含屏幕坐标pixel、经纬度coordinate与选中元素的属性properties、originalEvent
onClusterClick单击图层不能在下钻聚合点事件object — 包含屏幕坐标pixel、经纬度coordinate与聚合数据data、originalEvent
onMouseMove鼠标移动事件object — 包含屏幕坐标pixel、经纬度coordinate与选中元素的属性properties、originalEvent
onMouseEnter鼠标移进事件object — 包含屏幕坐标pixel、经纬度coordinate与选中元素的属性properties、originalEvent
onMouseLeave鼠标移出元素事件MapMouseEvent

METHODS

名称描述定义
exportToGeoJson将本图层数据导出为geojson格式数据文本 若图层数据源来自source组件或为url,则输出null()=> object | null