Skip to content

图层

创建矢量图层

createVectorLayer

点我查看代码
vue
<script lang="ts" setup>
import { createVectorLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Feature, Map as OLMap } from 'ol'
import { LineString, Point, Polygon } from 'ol/geom'

const olMap = new OLMap()
const { source, layer } = createVectorLayer()
olMap.addLayer(layer)
const point = new Feature({
  geometry: new Point([120, 30]),
})
source.addFeature(point)

const line = new Feature({
  geometry: new LineString([[120.1, 30.1], [120.2, 30.2]]),
})
source.addFeature(line)

const polygon = new Feature({
  geometry: new Polygon([[[120.11, 30.15], [120.15, 30.25], [120.1, 30.2], [120.11, 30.15]]]),
})
source.addFeature(polygon)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-400px" />
</template>

添加 OpenStreetMap 地图

createOpenStreetMapLayer

点我查看代码
vue
<script lang="ts" setup>
import { createOpenStreetMapLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()
const osmLayer = createOpenStreetMapLayer()
olMap.addLayer(osmLayer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-400px" />
</template>

添加天地图

createTianDiTuLayer

点我查看代码
vue
<script lang="ts" setup>
import { createTianDiTuLayer, EPSG_3857, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()
const layer = createTianDiTuLayer({
  type: 'img',
  key: '8a684acb7b9d38ba08adf8035d0262ee',
  projection: EPSG_3857,
})
const label = createTianDiTuLayer({
  type: 'cia',
  key: '8a684acb7b9d38ba08adf8035d0262ee',
  projection: EPSG_3857,
})
olMap.addLayer(layer)
olMap.addLayer(label)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-300px" />
</template>

添加 Bing 地图

createBingLayer

点我查看代码
vue
<script lang="ts" setup>
import { createBingLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()
const bingLayer = createBingLayer({
  key: 'AtmBUmOPFg6c61ynLhIbjvrKfuXkMw1lCMTlLh9ALY47Llyetb6lgyRMitoPxKZo',
  name: 'RoadOnDemand',
})
olMap.addLayer(bingLayer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-300px" />
</template>

添加 XYZ 图层

createXYZLayer — 通用 XYZ 瓦片图层,适用于任何标准 XYZ 瓦片服务。

点我查看代码
vue
<script lang="ts" setup>
import { createXYZLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()

// 使用 CartoDB 浅色底图(免费,无需 key)
const xyzLayer = createXYZLayer({
  sourceOptions: {
    url: 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
    projection: EPSG_4326,
  },
})
olMap.addLayer(xyzLayer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-400px" />
</template>

添加 PMTiles 图层

createPMTilesLayer — 加载 PMTiles 格式的矢量瓦片。

点我查看代码
vue
<script lang="ts" setup>
import { createPMTilesLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()

// 使用 protomaps 公开示例 PMTiles 文件(佛罗伦萨矢量地图)
const pmtilesLayer = createPMTilesLayer({
  url: 'https://air.mtn.tw/flowers.pmtiles',
})
olMap.addLayer(pmtilesLayer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[121.525398, 25.014998]" :zoom="13" class="w-100% h-400px" />
</template>

添加 WebGL 矢量图层

createWebGLVectorLayer — 使用 GPU 渲染的大数据量矢量图层,样式通过 FlatStyle 表达式定义。

点我查看代码
vue
<script lang="ts" setup>
import { createWebGLVectorLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Feature, Map as OLMap } from 'ol'
import { Point } from 'ol/geom'

const olMap = new OLMap()

interface PointData {
  name: string
  color: string
  size: number
}

const features: Feature<Point>[] = [
  { name: '北京', lon: 116.39, lat: 39.91, color: '#e74c3c', size: 20 },
  { name: '上海', lon: 121.47, lat: 31.23, color: '#3498db', size: 16 },
  { name: '广州', lon: 113.26, lat: 23.13, color: '#2ecc71', size: 14 },
  { name: '成都', lon: 104.07, lat: 30.57, color: '#f39c12', size: 14 },
  { name: '武汉', lon: 114.30, lat: 30.60, color: '#9b59b6', size: 12 },
].map((d) => {
  return new Feature<Point>({
    geometry: new Point([d.lon, d.lat]),
    name: d.name,
    color: d.color,
    size: d.size,
  })
})

const { source, layer } = createWebGLVectorLayer({
  style: {
    'circle-radius': ['get', 'size'],
    'circle-fill-color': ['get', 'color'],
    'circle-stroke-color': '#ffffff',
    'circle-stroke-width': 2,
    'circle-opacity': 0.85,
  },
  sourceOptions: {
    features,
  },
})
olMap.addLayer(layer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[115, 32]" :zoom="5" class="w-100% h-400px" />
</template>

添加 Canvas 图层

createCanvasLayer — 通过 refresh 回调在 Canvas 上逐帧绘制,适合自定义渲染、动画覆盖层等场景。

点我查看代码
vue
<script lang="ts" setup>
import { createCanvasLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Map as OLMap } from 'ol'

const olMap = new OLMap()

// 使用 Canvas 图层绘制一个简单的标注覆盖层
createCanvasLayer(olMap, (frameState) => {
  const canvas = new OffscreenCanvas(300, 100)
  const ctx = canvas.getContext('2d')!

  // 背景
  ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'
  ctx.beginPath()
  ctx.roundRect(0, 0, 280, 70, 8)
  ctx.fill()

  // 文字
  ctx.fillStyle = '#ffffff'
  ctx.font = '16px sans-serif'
  ctx.fillText('Canvas 图层示例', 16, 30)
  ctx.fillStyle = '#aabbcc'
  ctx.font = '12px sans-serif'
  ctx.fillText('通过 refresh 回调逐帧绘制', 16, 52)

  return {
    imageBitmap: canvas.transferToImageBitmap(),
    dpi: window.devicePixelRatio,
  }
})
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-400px" />
</template>

添加热力图

createHeatmapLayer — 热力图图层,数据点为 Feature,通过 weight 字段控制权重。

点我查看代码
vue
<script lang="ts" setup>
import { createHeatmapLayer, EPSG_4326, OlMap } from '@summeruse/ol'
import { Feature, Map as OLMap } from 'ol'
import { Point } from 'ol/geom'

const olMap = new OLMap()

// 生成随机热力点
const points: Feature<Point>[] = []
// 中心区域密集
for (let i = 0; i < 50; i++) {
  const lon = 120 + (Math.random() - 0.5) * 2
  const lat = 30 + (Math.random() - 0.5) * 2
  const feature = new Feature({
    geometry: new Point([lon, lat]),
    weight: Math.random(),
  })
  points.push(feature)
}
// 外围稀疏点
for (let i = 0; i < 20; i++) {
  const lon = 120 + (Math.random() - 0.5) * 5
  const lat = 30 + (Math.random() - 0.5) * 5
  const feature = new Feature({
    geometry: new Point([lon, lat]),
    weight: Math.random() * 0.5,
  })
  points.push(feature)
}

const { layer, source } = createHeatmapLayer({
  blur: 20,
  radius: 15,
  weight: 'weight',
})
source.addFeatures(points)
olMap.addLayer(layer)
</script>

<template>
  <OlMap :ol-map :projection="EPSG_4326" :center="[120, 30]" :zoom="10" class="w-100% h-400px" />
</template>

API

底图图层

名称类型说明
createOpenStreetMapLayer(data?: CreateOpenStreetMapLayerOptions) => TileLayer创建 OpenStreetMap 瓦片图层
createTianDiTuLayer(data: CreateTianDiTuLayerOptions) => TileLayer创建天地图瓦片图层
createTianDiTuUrl(data: CreateTianDiTuUrlOptions) => string生成天地图瓦片 URL
createBingLayer(options: CreateBingLayerOptions) => TileLayer创建 Bing 地图瓦片图层
createXYZLayer(options: XYZLayerOptions) => TileLayer创建通用 XYZ 瓦片图层
createPMTilesLayer(config: PMTilesLayerOptions) => TileLayer创建 PMTiles 矢量瓦片图层

矢量图层

名称类型说明
createVectorSource<T>(options?: VectorSourceOptions<T>) => VectorSource创建矢量数据源
createVectorLayer<T>(options?: VectorLayerOptions<T>) => { source, layer }创建矢量图层,返回 { source, layer }
createWebGLVectorLayer<T>(options: WebGLVectorLayerOptions<T>) => { source, layer }创建 WebGL 矢量图层,使用 FlatStyle,返回 { source, layer }
createHeatmapLayer(options?: createHeatmapLayerOptions) => { layer, source }创建热力图图层,返回 { layer, source }

画布图层

名称类型说明
createCanvasLayer(olMap: OLMap, refresh: (frameState) => ..., options?: CanvasLayerOptions) => { layer }创建 Canvas 图层,通过 refresh 回调逐帧绘制

主要类型

类型说明
T_MAP_TYPE'vec'|'cva'|'img'|'cia'|'ter'|'cta'|'ibo' 天地图图层类型
CreateTianDiTuUrlOptions天地图 URL 配置,含 typekeyprojection
CreateTianDiTuLayerOptions天地图图层配置,继承 CreateTianDiTuUrlOptions,额外含 layerOptionssourceOptions
CreateBingLayerOptionsBing 图层配置,含 name(影像集名称)、keylayerOptionssourceOptions
CreateOpenStreetMapLayerOptionsOSM 图层配置,含 layerOptionssourceOptions
TileLayerOptionsOpenLayers TileLayer 构造参数类型
XYZ_SourceOptionsOpenLayers XYZ 构造参数类型
XYZLayerOptionsXYZ 图层配置,含 sourceOptionsTileLayerOptions
BingMapsSourceOptionsOpenLayers BingMaps 构造参数类型
OpenStreetMapSourceOptionsOpenLayers OSM 构造参数类型
VectorSourceOptions<T>VectorSource 构造参数类型
VectorLayerOptions<T>矢量图层配置,含 styleOptionssourceOptions 快捷字段
WebGLVectorLayerOptions<T>WebGL 矢量图层配置,必须含 style: FlatStyleLike,可选 sourceOptions
PMTilesLayerOptionsPMTiles 配置,含 urlsourceOptions
CanvasLayerOptionsCanvas 图层配置(OpenLayers Layer 构造参数)
createHeatmapLayerOptions热力图配置,含 sourceOptionsHeatmapLayerOptions
HeatmapLayerOptionsOpenLayers HeatmapLayer 构造参数类型

源代码

点我查看代码
ts
import type { Feature } from 'ol'
import type { Geometry } from 'ol/geom'
import type { FrameState } from 'ol/Map'
import type { FlatStyleLike } from 'ol/style/flat'
import type { OLMap } from '@/types'
import type { StyleOptions } from '@/utils/style'
import { Layer, Tile as TileLayer } from 'ol/layer'
import HeatmapLayer from 'ol/layer/Heatmap'
import VectorLayer from 'ol/layer/Vector'
import WebGLVectorLayer from 'ol/layer/WebGLVector'
import { BingMaps, OSM, Source, XYZ } from 'ol/source'
import ImageTileSource from 'ol/source/ImageTile'
import VectorSource from 'ol/source/Vector'
import { PMTiles } from 'pmtiles'
import { EPSG_3857 } from '@/constants/projection'
import { createStyle } from '@/utils/style'
import { createTileGrid } from '../projection'

export type T_MAP_TYPE = 'vec' | 'cva' | 'img' | 'cia' | 'ter' | 'cta' | 'ibo'

export interface CreateTianDiTuUrlOptions {
  url?: string
  key: string
  type: T_MAP_TYPE
  projection?: 'EPSG:3857' | 'EPSG:4326'
}

export function createTianDiTuUrl(data: CreateTianDiTuUrlOptions) {
  const { type = 'img', projection = EPSG_3857, key } = data
  const url = data.url || `https://t{0-4}.tianditu.gov.cn`
  const suffix
    = `&tk=${key
    // cSpell:disable-next-line
    }&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}`
  const proj = projection === EPSG_3857 ? 'w' : 'c'
  return `${url}/${type}_${proj}/wmts?LAYER=${type}${suffix}`
}

export type TileLayerOptions = Partial<ConstructorParameters<typeof TileLayer>[0]>

export type XYZ_SourceOptions = Partial<ConstructorParameters<typeof XYZ>[0]>

export type CreateTianDiTuLayerOptions = CreateTianDiTuUrlOptions & {
  layerOptions?: TileLayerOptions
  sourceOptions?: XYZ_SourceOptions
}

export function createTianDiTuLayer(data: CreateTianDiTuLayerOptions) {
  const { layerOptions, sourceOptions, ...options } = data
  return new TileLayer({
    source: new XYZ({
      url: createTianDiTuUrl(options),
      projection: options.projection,
      crossOrigin: 'Anonymous',
      ...sourceOptions,
    }),
    ...layerOptions,
  })
}

export type BingMapsSourceOptions = Partial<ConstructorParameters<typeof BingMaps>[0]>

export interface CreateBingLayerOptions {
  name: string
  key: string
  layerOptions?: TileLayerOptions
  sourceOptions?: BingMapsSourceOptions
}

export function createBingLayer({
  name,
  key,
  layerOptions,
  sourceOptions,
}: CreateBingLayerOptions) {
  const layer = new TileLayer({
    source: new BingMaps({
      key,
      imagerySet: name,
      placeholderTiles: false,
      ...sourceOptions,
    }),
    ...layerOptions,
  })
  return layer
}

export type OpenStreetMapSourceOptions = Partial<ConstructorParameters<typeof OSM>[0]>

export interface CreateOpenStreetMapLayerOptions {
  layerOptions?: TileLayerOptions
  sourceOptions?: OpenStreetMapSourceOptions
}

export function createOpenStreetMapLayer(data?: CreateOpenStreetMapLayerOptions) {
  const { layerOptions, sourceOptions } = data || {}
  const layer = new TileLayer({
    source: new OSM({
      crossOrigin: 'Anonymous',
      ...sourceOptions,
    }),
    ...layerOptions,
  })
  return layer
}

export type _VectorSourceOptions<T extends Geometry = Geometry> = ConstructorParameters<typeof VectorSource<Feature<T>>>[0]

export type VectorSourceOptions<T extends Geometry = Geometry> = _VectorSourceOptions<T>

export function createVectorSource<T extends Geometry = Geometry>(options?: VectorSourceOptions<T>) {
  return new VectorSource<Feature<T>>({
    ...options,
  })
}

export type _VectorLayerOptions<T extends Geometry = Geometry> = ConstructorParameters<typeof VectorLayer<VectorSource<Feature<T>>>>[0]

export type VectorLayerOptions<T extends Geometry = Geometry> = _VectorLayerOptions<T> & {
  styleOptions?: StyleOptions
  sourceOptions?: VectorSourceOptions<T>
}

export function createVectorLayer<T extends Geometry = Geometry>(options?: VectorLayerOptions<T>) {
  const { styleOptions, sourceOptions, style: _style, source: _source, ...restOptions } = options || {}
  const style = _style || (styleOptions ? createStyle(styleOptions) : undefined)
  const source = _source || createVectorSource<T>(sourceOptions)
  const layer = new VectorLayer({
    ...restOptions,
    source,
    style,
  })
  return {
    source,
    layer,
  }
}

export type _WebGLVectorLayerOptions<T extends Geometry = Geometry> = ConstructorParameters<typeof WebGLVectorLayer<VectorSource<Feature<T>>>>[0]

export type WebGLVectorLayerOptions<T extends Geometry = Geometry> = _WebGLVectorLayerOptions<T> & {
  style: FlatStyleLike
  sourceOptions?: VectorSourceOptions<T>
}

export function createWebGLVectorLayer<T extends Geometry = Geometry>(options: WebGLVectorLayerOptions<T>) {
  const { style, source: _source, sourceOptions, ...restOptions } = options
  const source = _source || createVectorSource<T>(sourceOptions)
  const layer = new WebGLVectorLayer({
    ...restOptions,
    source,
    style,
  })
  return {
    source,
    layer,
  }
}

export type ImageTileSourceOptions = ConstructorParameters<typeof ImageTileSource>[0]

export type PMTilesLayerOptions = TileLayerOptions & {
  url: string
  sourceOptions?: ImageTileSourceOptions
}

function loadImage(src: string) {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const img = new Image()
    img.addEventListener('load', () => resolve(img))
    img.addEventListener('error', () => reject(new Error('load failed')))
    img.src = src
  })
}

export function createPMTilesLayer(config: PMTilesLayerOptions) {
  const { url, sourceOptions, ...layerOptions } = config
  const tiles = new PMTiles(url)
  const tileGrid = createTileGrid(sourceOptions?.projection)
  return new TileLayer({
    ...layerOptions,
    source: new ImageTileSource({
      async loader(z, x, y, { signal }) {
        const response = await tiles.getZxy(z, x, y, signal)
        const blob = new Blob([response!.data])
        const src = URL.createObjectURL(blob)
        const image = await loadImage(src)
        URL.revokeObjectURL(src)
        return image
      },
      crossOrigin: 'anonymous',
      tileGrid,
      ...sourceOptions,
    }),
  })
}

export type XYZLayerOptions = TileLayerOptions & {
  sourceOptions: XYZ_SourceOptions
}

export function createXYZLayer({ sourceOptions, ...layerOptions }: XYZLayerOptions) {
  const tileGrid = createTileGrid(sourceOptions?.projection)
  return new TileLayer({
    ...layerOptions,
    source: new XYZ({
      crossOrigin: 'anonymous',
      ...sourceOptions,
      tileGrid,
    }),
  })
}

export type CanvasLayerOptions = ConstructorParameters<typeof Layer>[0]

export function createCanvasLayer(olMap: OLMap, refresh: (frameState: FrameState) =>
  | {
    imageBitmap: ImageBitmap
    dpi: number
  }
  | boolean
  | undefined, options?: CanvasLayerOptions) {
  const container = document.createElement('div')
  container.style.position = 'absolute'
  container.style.width = '100%'
  container.style.height = '100%'
  const _canvas = document.createElement('canvas')
  _canvas.style.width = '100%'
  _canvas.style.height = '100%'
  container.appendChild(_canvas)
  const ctx = _canvas.getContext('2d')!
  const config = {
    size: [0, 0],
    dpi: window.devicePixelRatio,
  }
  const source = new Source({})
  const layer = new Layer({
    render: (frameState) => {
      const size = frameState.size
      const res = refresh(frameState)

      if (res === false) {
        return container
      }

      if (res === true || !res) {
        ctx.setTransform(1, 0, 0, 1, 0, 0)
        ctx.clearRect(0, 0, _canvas.width, _canvas.height)
        return container
      }

      if (res) {
        const { imageBitmap, dpi } = res
        if (dpi !== config.dpi || size[0]! !== config.size[0] || size[1]! !== config.size[1]) {
          config.size = [size[0]!, size[1]!]
          config.dpi = dpi
          _canvas.width = size[0]! * dpi
          _canvas.height = size[1]! * dpi
        }
        ctx.setTransform(1, 0, 0, 1, 0, 0)
        ctx.clearRect(0, 0, _canvas.width, _canvas.height)
        ctx.drawImage(imageBitmap, 0, 0, _canvas.width, _canvas.height)
        imageBitmap.close()
      }
      return container
    },
    source,
    ...options,
  })
  olMap.addLayer(layer)

  return { layer }
}

export type HeatmapLayerOptions = ConstructorParameters<typeof HeatmapLayer>[0]

export type createHeatmapLayerOptions = HeatmapLayerOptions & {
  sourceOptions?: VectorSourceOptions
}

export function createHeatmapLayer(options?: createHeatmapLayerOptions) {
  const { sourceOptions, ...restOptions } = options || {}
  const source = createVectorSource(sourceOptions)
  const layer = new HeatmapLayer({
    ...restOptions,
    source,
  })
  return { layer, source }
}

Released under the ISC License.