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

Selected Element Information:

lng: lat:

properties:{}



Example Source Code
vue
<template>
  <div style="height: 500px">
    <div style="height: 400px">
      <mb-map
        :zoom="mapZoom"
        :center="mapCenter"
        :glyphs="__RESOURCE_URL__ + 'fonts/{range}.pbf'"
        :sprite="__RESOURCE_URL__ + 'sprites/sprite'"
        @created="mapCreatedHandler"
      >
        <mb-tianditu-layer :types="['vec']" />
        <mb-symbol-layer
          :data="symbolDataSource"
          text-field="label"
          icon-image-field="icon"
          text-halo-color="#9e9e9e"
          :text-halo-width="1"
          :text-offset="[0, -1]"
          :cluster="false"
          @mousemove="clickLayerHandler"
          @mouseleave="leaveLayerHandler"
        />
      </mb-map>
    </div>
    <div style="height: 60px; margin: 10px 10px">
      <p>Selected Element Information:</p>
      <p class="text-muted">lng:{{ lng }} lat:{{ lat }}</p>
      <p class="text-muted">properties:{{ JSON.stringify(properties) }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { Map } from 'mapbox-gl'

const symbolDataSource = ref([])
const lng = ref('')
const lat = ref('')
const properties = ref<Record<string, any>>({})
const mapCenter = [107.5, 37.5]
const mapZoom = 3.5

function getRandomArbitrary(min: number, max: number) {
  return Math.random() * (max - min) + min
}
const mapCreatedHandler = (map: Map) => {
  map.rotateTo(180, { duration: 10000 })
}
const clickLayerHandler = (payload) => {
  lng.value = payload.coordinates[0]
  lat.value = payload.coordinates[1]
  properties.value = payload.properties
}
const leaveLayerHandler = () => {
  lng.value = ''
  lat.value = ''
  properties.value = {}
}

onMounted(() => {
  const data = []
  for (let i = 0; i < 2000; i++) {
    data.push({
      coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
      properties: {
        label: `Airport-${i}`,
        icon: 'airport-15',
      },
    })
  }
  symbolDataSource.value = data
})
</script>

Clustered Example

Example Source Code
vue
<template>
  <div style="height: 400px" class="vw-full vh-full">
    <mb-map
      :zoom="4"
      :center="[107.5, 37.5]"
      :glyphs="__RESOURCE_URL__ + 'fonts/{range}.pbf'"
      :sprite="__RESOURCE_URL__ + 'sprites/sprite'"
    >
      <mb-tianditu-layer :types="['vec']" />
      <mb-symbol-layer
        :data="symbolDataSource"
        text-field="label"
        icon-image-field="icon"
        :text-offset="[0, -1]"
        :cluster="true"
        :spiderify="true"
      />
    </mb-map>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'

const symbolDataSource = ref([])

function getRandomArbitrary(min: number, max: number) {
  return Math.random() * (max - min) + min
}

onMounted(() => {
  const data = []
  for (let i = 0; i < 2000; i++) {
    data.push({
      coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
      properties: {
        label: `Airport-${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',
      },
    })
  }
  symbolDataSource.value = data
})
</script>

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
vue
<template>
  <div style="height: 400px" class="vw-full vh-full">
    <mb-map
      :zoom="5"
      :center="[116.678, 31.456]"
      :glyphs="__RESOURCE_URL__ + 'fonts/{range}.pbf'"
      :sprite="__RESOURCE_URL__ + 'sprites/sprite'"
    >
      <mb-tianditu-layer :types="['vec']" />
      <mb-image-loader :images="images" />
      <mb-symbol-layer
        :data="symbolDataSource"
        text-field="label"
        icon-image-field="icon"
        :cluster-icons="clusterIcons"
        :cluster-icon-size="0.5"
        :cluster-icon-offset="[1, 5]"
        :cluster="true"
        :spiderify="true"
      />
      <mb-symbol-layer
        :data="symbolDataSource2"
        text-field="label"
        icon-image-field="icon"
        :cluster-icons="clusterIcons2"
        :cluster-icon-size="0.5"
        :cluster-icon-offset="[1, 45]"
        :cluster="true"
        :spiderify="true"
      />
    </mb-map>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
const images = [
  {
    name: 'cluster-icon',
    type: 'link',
    url: `${__RESOURCE_URL__}images/common_toget.png`,
  },
  {
    name: 'cluster-icon2',
    type: 'link',
    url: `${__RESOURCE_URL__}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 symbolDataSource = ref([])
const symbolDataSource2 = ref([])

onMounted(() => {
  const data = [],
    data2 = []
  for (let i = 0; i < 13; i++) {
    data.push({
      coordinates: [116.678, 31.456],
      properties: {
        label: `same-${i}`,
        icon: 'airport-15',
      },
    })
    data2.push({
      coordinates: [113.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: [113.798, 31.956],
      properties: {
        label: `same-2-${i}`,
        icon: 'airport-15',
      },
    })
  }
  symbolDataSource.value = data
  symbolDataSource2.value = data2
})
</script>

Animation Example

Example Source Code
vue
<template>
  <div style="height: 400px" class="vw-full vh-full">
    <mb-map
      :zoom="11"
      :center="[116.32, 39.9]"
      :glyphs="__RESOURCE_URL__ + 'fonts/{range}.pbf'"
      :sprite="__RESOURCE_URL__ + 'sprites/sprite'"
    >
      <div style="position: absolute; top: 5px; left: 5px">
        <button class="primary" @click="start">Restart</button>
      </div>
      <mb-tianditu-layer :types="['vec']" />
      <mb-image-loader :images="images" />
      <mb-polyline-layer :data="polylineCoordinates" :width="3" color="blue" />
      <mb-symbol-layer
        :data="symbolDataSource"
        icon-image-field="icon"
        icon-anchor="center"
        :icon-size="0.5"
        icon-rotation-alignment="map"
        icon-allow-overlap
        :icon-rotate="['get', 'bearing']"
        icon-ignore-placement
      />
    </mb-map>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import * as turf from '@turf/turf'

const images = [
  {
    name: 'cluster-icon',
    type: 'link',
    url: `${__RESOURCE_URL__}images/common_toget.png`,
  },
  {
    name: 'cluster-icon2',
    type: 'link',
    url: `${__RESOURCE_URL__}images/eme_team_soc_toget.png`,
  },
]
let counter = 0
const steps = 9
const polylineCoordinates = [
  {
    coordinates: [
      [116.28, 39.91],
      [116.29, 39.91],
      [116.32, 39.92],
      [116.33, 39.91],
      [116.32, 39.9],
      [116.32, 39.89],
      [116.3, 39.89],
      [116.29, 39.89],
      [116.27, 39.9],
    ],
  },
]
const symbolDataSource = ref([
  {
    coordinates: [116.28, 39.91],
    properties: {
      label: ``,
      icon: 'cluster-icon',
      iconSize: 0.5,
      bearing: 0,
    },
  },
])

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)
  )

  // 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++
}
</script>
Example Source Code
vue
<template>
  <div style="height: 400px">
    <mb-map
      :zoom="mapZoom"
      :center="mapCenter"
      :glyphs="__RESOURCE_URL__ + 'fonts/{range}.pbf'"
      :sprite="__RESOURCE_URL__ + 'sprites/sprite'"
      @created="mapCreatedHandler"
    >
      <mb-tianditu-layer :types="['vec']" />
      <mb-symbol-layer
        :data="symbolDataSource"
        icon-image-field="icon"
        :cluster="false"
      />
      <mb-map-popup
        v-for="s in symbolDataSource"
        :key="s.properties.id"
        :coordinates="s.coordinates"
        :offset="[0, -10]"
        :show-tip="false"
        anchor="bottom"
      >
        <div
          style="
            padding: 4px;
            background-color: white;
            box-shadow: 10px 5px 10px grey;
          "
        >
          {{ s.properties.label }}
        </div>
      </mb-map-popup>
    </mb-map>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { Map } from 'mapbox-gl'

const symbolDataSource = ref([])
const mapCenter = [107.5, 37.5]
const mapZoom = 3.5

function getRandomArbitrary(min: number, max: number) {
  return Math.random() * (max - min) + min
}
const mapCreatedHandler = (map: Map) => {
  map.rotateTo(180, { duration: 10000 })
}

onMounted(() => {
  const data = []
  for (let i = 0; i < 10; i++) {
    data.push({
      coordinates: [getRandomArbitrary(95, 120), getRandomArbitrary(30, 42)],
      properties: {
        id: String(Math.random()),
        label: `Airport-${i}`,
        icon: 'airport-15',
      },
    })
  }
  symbolDataSource.value = data
})
</script>

API

PROPS

名称描述类型默认值
idLayer IDstring-
clusterWhether to enable clusteringbooleanfalse
cluster-iconsConfiguration information for cluster icons. See example for detailsIndexAny[]见示例说明
cluster-max-zoomThe maximum zoom level at which clustering is enablednumber14
cluster-min-pointsThe minimum number of points required for clusteringnumber2
cluster-radiusClustering radius in pixelsnumber50
cluster-icon-sizeIcon scaling factornumber1
cluster-icon-translateIcon anchor translation. Positive values shift right/downnumber[][0,0]
cluster-icon-offsetDistance between the icon and its anchor. Positive values shift right/downnumber[][0,0]
cluster-icon-anchorIcon 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-
geo-json-data-sourceGeoJSON 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-
icon-allow-overlapWhether to allow icon overlapbooleanfalse
icon-anchorIcon anchor positionstring("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
icon-colorIcon color. Only effective for SDF iconsstring#000000
icon-imageIcon resource. Pre-loaded image namestring / AnyArr-
icon-image-fieldIcon image field name. Will be read from the dataSource properties, overriding the iconImage propertystring-
icon-offsetDistance between the icon and its anchor. Positive values shift right/downnumber[][0,0]
icon-opacityIcon opacitynumber1
icon-optionalIf true, the icon will only be displayed if it doesn't collide with other icons, but the text does not collide with other textbooleanfalse
icon-rotateIcon rotation anglenumber0
icon-rotation-alignment-stringauto
icon-ignore-placement-booleanfalse
icon-sizeIcon scaling factornumber1
icon-translateIcon 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
source-idID of the layer's source. If this ID is set, the geoJsonDataSource and data properties are ignoredstring-
source-layer-nameLayer name in the source data. This property is effective when sourceId is setstring-
textText value. Only effective if textFiled is not specifiedstring-
text-allow-overlapWhether to allow text overlapbooleanfalse
text-anchorText anchor positionstring("center", "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right")center
text-colorText colorstring#000000
text-fieldText field value/key. Will be taken from the dataSource propertiesstring-
text-fontText fontstring[]["Open Sans SemiBold"]
text-halo-blurText halo blur effect in pixelsnumber0
text-halo-colorText halo colorstringrgba(0, 0, 0, 0)
text-halo-widthText halo width in pixelsnumber0
text-letter-spacingText letter spacing in em unitsnumber0
text-line-heightLine height in em unitsnumber1.2
text-max-widthMaximum text width in em unitsnumber10
text-offsetDistance between the text and its anchor. Positive values shift right/down in pixelsnumber[][0,0]
text-opacityText opacitynumber1
text-optionalIf true, the icon will be displayed only if the text collides with other text but the icon does not collide with other iconsbooleanfalse
text-sizeText font size in pixelsnumber16
text-translateText anchor translation. Positive values shift right/down in pixelsnumber[][0,0]
text-paddingText paddingnumber2
spiderifyWhether points with the same latitude and longitude can be spread out (spiderified)booleanfalse
max-leafes-tospiderifyMaximum number of points to spiderifynumber50
distance-between-spider-pointsDistance between spiderified pointsnumber50
spider-leg-colorColor of the lines connecting the spiderified pointsstring#B6CBED
spider-leg-widthWidth of the lines connecting the spiderified pointsnumber3
compare-precisionThe number of decimal places used when comparing clustered points for equality. Decimal places beyond comparePrecision will be truncatednumber5
cluster-spiral-pointsThe 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
createdMap initialization completed event-
clickLayer click eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
clusterclickClick event for non-drillable cluster points in layerobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), cluster data, and originalEvent
mousemoveMouse move eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
mouseenterMouse enter eventobject — Contains screen coordinates (pixel), longitude/latitude (coordinate), selected element properties, and originalEvent
mouseleaveMouse leave element eventMapMouseEvent

SLOTS

NameDescription

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