map-ol-utils

中文文档
1.2.10发布于 2026/11/04MITTypeScriptESM + CJS

OpenLayers实用程序与浏览器端天地图私有VTS的加载和转换

中文使用文档
文档目录70 节
  1. 特性
  2. 项目结构
  3. 安装
  4. 快速开始
  5. 天地图私有 VTS
  6. 一、地图实例( map/create-map.ts )
  7. createMapInstance(config?)
  8. createMap(config?)
  9. getMapInstance()
  10. disposeMap(mapInstance?)
  11. resetMap(config?, mapInstance?)
  12. resetZoomAndCenter(center?, mapInstance?)
  13. instance
  14. 二、底图与图层名管理( layer/base-layer.ts )
  15. createBaseLayer(layerName?)
  16. createMapWorldLayer(layerName, tk?, config?)
  17. createZheJiangLayer(layerName?, tk?)
  18. getLayerByName(name, mapInstance?)
  19. removeLayerByName(name, mapInstance?)
  20. changeLayersByNames(activeNames, allLayerNames)
  21. 三、矢量图层( layer/vector-layer.ts )
  22. createBusinessLayer(dataList, config, mapInstance?)
  23. createVectorLineLayer(dataList?, config, mapInstance?) / cr…
  24. appendFeaturesByLayer(targetLayer, dataList, crsForm?)
  25. 四、海量点 MassMarks( layer/mass-marks.ts )
  26. buildMassMarksStyles(getImgUrlFn)
  27. createMassMarksLayer(dataList, styleList?, config?)
  28. addMassMarksLayer(getImgUrlFn)
  29. addFeaturesByLayer(targetLayer, dataList, config)
  30. updateMarksLayersByStatus(checkedStatusList?, mapInstance?)
  31. changeFeaturesVisibleByIds(layerName, visibleIdSet, mapInst…
  32. 五、聚合图层( layer/cluster-layer.ts )
  33. createClusterLayer(dataList, config, mapInstance?)
  34. 聚合点击数据
  35. 六、交互与选中( interaction/select.ts )
  36. createSelectByLayer(vectorLayer, mapInstance?)
  37. setFeaturesActive(selector, active?, mapInstance?)
  38. activeFeatureByUid(uid, mapInstance?)
  39. resetActiveFeature(mapInstance?) / resetActiveVector(mapIns…
  40. 七、覆盖物与视野( overlay/overlay.ts 、 map/view-fit.ts )
  41. createOverlay(config?)
  42. removeOverlays(key?, mapInstance?) / removeAllLayers(mapIns…
  43. createAnimationMarker(imgUrl, data, mapInstance?)
  44. setFitViewByCoords / setFitViewByGeom / setFitViewByLayer /…
  45. getLayersDataList(mapInstance?)
  46. 八、WMTS( layer/ogc-utils.ts )
  47. createWmtsLayer(serverUrl)
  48. 九、事件总线( emitter )
  49. 十、扩展能力
  50. 天地图组合与栅格处理
  51. 标记与选择
  52. 绘制、GeoJSON 与视野
  53. 航线控制器
  54. 模式与生命周期
  55. 新增航点回调
  56. 普通航点删除回调
  57. 状态订阅
  58. 业务面板与输入
  59. 按 UUID 选中航点
  60. 航线样式与数据约定
  61. 导入航点
  62. 更新航点扩展属性
  63. 批量更新全部航点
  64. 导出航点
  65. 编辑与飞行配置
  66. 动态更新飞行速度
  67. 十一、类型定义( core/types.ts , export * from './core/types' )
  68. 图层约定属性
  69. 构建
  70. 注意事项

map-ol-utils

目录:packages/map-utils | npm 包名:map-ol-utils

基于 OpenLayers 的地图工具库,封装了地图实例管理、底图创建、矢量/面/线/聚合/海量点图层、要素交互选择、视野自适应、WMTS 接入,以及天地图私有 VTS 的浏览器加载与标准 MVT 转换能力。

特性

  • 单例与独立地图:默认沿用模块单例;地图相关工具可显式传入目标地图,隔离选择、图层、覆盖物与视野。
  • 多种底图:杭州规自瓦片、全国天地图(XYZ,支持在线与本地栅格)、浙江省天地图(XYZ)。
  • 业务矢量图层:根据业务数据自动推断几何类型(线/面)并生成 GeoJSON 图层,支持着色映射、点击高亮。
  • 海量点 MassMarks:基于状态值的多套样式(默认 + 激活),可附带伴生线/面图层。
  • 聚合图层:基于 ol/source/Cluster,半径/颜色随聚合数量动态计算。
  • 坐标系转换:内部统一通过 gcoord 转换到 WGS84(EPSG:4326)。
  • 事件总线:内置 mitt 实例 emitter,用于派发要素选中等事件。
  • 扩展能力:提供天地图组合、栅格着色、标记、绘制量算、GeoJSON 图层和多图层选择能力。
  • 私有 VTS 转换:通过 tdt-vts-core 复用 PK 编解码、私有 Protobuf/MVT 转换和 Web Worker 解码,本包只保留 OpenLayers 适配。

项目结构

packages/map-utils/
├── src/
│   ├── core/                    # 类型、常量、事件总线与地图上下文
│   ├── interaction/             # 绘制和要素选择交互
│   ├── layer/                   # 底图、矢量、聚合、海量点、WMTS 与图层生命周期
│   ├── map/                     # 地图实例管理与视野适配
│   ├── overlay/                 # Overlay 与动画标注
│   ├── tdt-vts/                 # 天地图私有 VTS 的公共重导出与 OL 适配
│   └── index.ts                 # 唯一公共 API 入口
├── test/
│   ├── architecture.test.ts     # 模块边界与架构约束
│   └── compatibility.test.ts    # 公共 API 兼容性测试
├── package.json                 # 包入口、依赖和构建脚本
├── tsconfig.json                # TypeScript 配置
├── tsdown.config.ts             # ESM/CJS 与类型声明构建配置
└── README.md                    # 安装、API 与使用约定

航线内部按职责拆分:interaction/route.ts 维护航线状态、公开交互入口、事务与模式生命周期;interaction/route-control.ts 管理实时飞行器、动作集合和动画帧;interaction/route-editor.ts 管理选择、拖动、插点、删除、高度调整与编辑资源清理;interaction/route-waypoints.ts 校验导入数据、创建航点、准备属性合并并转换导出数据;route-input.ts 管理键盘和面板输入、输入会话与地图指针恢复;route-style.ts 提供默认样式。调用方从包根导入 createRouteController,内部模块不作为公共入口。

安装

pnpm add map-ol-utils ol gcoord

Node.js 支持 ^22.18.0 || ^24.0.0 || >=26.0.0。ESM 与 CommonJS 入口共享同一运行时;CommonJS 使用 Node 的 require(esm) 加载能力,避免重复地图单例和事件总线。浏览器宿主继续使用 ESM。

ol 是对等依赖(要求 ^10.10.0),需由宿主项目安装。tdt-vts-core 是普通依赖,由包管理器自动安装。

快速开始

import {
  createMapInstance,
  createBusinessLayer,
  setFitViewByLayers,
  emitter
} from 'map-ol-utils'

const map = createMapInstance({ center: [120.15, 30.28], zoom: 11 })
map.setTarget('map') // 容器通过 setTarget 绑定,config 仅透传给 ol/View

const layer = createBusinessLayer(dataList, {
  key: 'coordinates',
  isLine: false,
  openClick: true,
  layerName: 'biz-layer',
  fillColor: '#4247DF',
  strokeColor: '#1DEBFF'
})
map.addLayer(layer)
setFitViewByLayers([layer], map) // 多个业务图层加载完成后可一起传入

emitter.on('selected-feature', (data) => console.log('选中要素:', data))

天地图私有 VTS

传入 Mapbox/MapLibre Style v8 JSON 地址或已加载的样式对象,创建 OpenLayers 图层:

import { createTdtVtsLayer } from 'map-ol-utils'

const layer = await createTdtVtsLayer({
  token: 'YOUR_TDT_TOKEN',
  hosts: ['https://tile0.tianditu.gov.cn'],
  style: '/styles/tdt-style.json'
})

map.addLayer(layer)
layer.dispose() // 同时释放解码 Worker

hosts 与 style 均为必填项。传入地址时,本包会请求并校验 Style v8 JSON,服务端需允许当前页面跨域访问;也可以直接传入包含 sources 和 layers 的样式对象。

业务调用只需要 createTdtVtsLayer();底层转换 API 保留用于兼容已有调用。


接口(API)详解

约定:独立工具函数默认操作 createMapInstance() 创建的模块单例;createMap() 返回独立地图,其挂载的图层方法只操作自身。下述接口的可选 mapInstance 默认为单例;用于事件回调时显式调用,例如 @click="resetActiveFeature()",避免事件对象被当成地图。

const left = createMap({ center: [120, 30] })
const right = createMap({ center: [121, 31] })
left.setTarget('left-map')
right.setTarget('right-map')
const layer = createBusinessLayer(
  dataList,
  { key: 'coordinates', isLine: false, openClick: true },
  left
)
left.addLayer(layer)
resetZoomAndCenter(undefined, left) // 恢复 left 自己的初始中心
disposeMap(left) // 不影响 right 或模块单例

一、地图实例(map/create-map.ts)

createMapInstance(config?)

创建并缓存地图单例;若已存在则直接返回,不会重建。

function createMapInstance(config?: Record<string, any>): CustomMap
参数类型默认说明
configobject{}透传给 createMap → ol/View 的视图配置

返回:CustomMap(OpenLayers Map 的扩展,附带 getLayerByName / removeLayerByName / changeLayersByNames 方法与 activeBaseLayer 字段)。

const map = createMapInstance({ center: [120.15, 30.28], zoom: 11 })
map.setTarget('map')

createMap(config?)

始终新建一个 CustomMap。默认挂载杭州规自暗色底图,禁用旋转/双击缩放,并在 pointermove 时根据是否命中要素切换鼠标指针;挂载的图层查询、切换和删除方法绑定到当前地图。config 与以下默认值合并后作为 ol/View 选项:

鼠标指针更新按帧合并;延迟回调执行时若地图已解除目标元素,会跳过命中检测与样式更新。

function createMap(config?: Record<string, any>): CustomMap
// 默认 View 配置:
// { zoom: 9.4, maxZoom: 18, minZoom: 9.4, center, baseLayer: 'vec-layer',
//   projection: 'EPSG:4326', layers: [createBaseLayer()],
//   constrainResolution: false, smoothExtentConstraint: false, tk }
参数类型默认说明
configobject{}ol/View 配置;可额外传 layers 覆盖默认底图、baseLayer 标记当前底图名
const map = createMap({ zoom: 12, center: [121.43, 28.66] })

getMapInstance()

function getMapInstance(): CustomMap

返回当前单例(等价于导出的 instance 变量)。

disposeMap(mapInstance?)

function disposeMap(mapInstance?: CustomMap): void

销毁指定地图,省略参数时销毁单例:清空覆盖物与交互,从实际所属的图层组移除叶子图层,释放渲染器、数据源和库创建的监听;保留旧渲染帧的 LayerState。setTarget('') 后销毁地图,仅目标是当前单例时清空 instance。未初始化或重复调用安全。

resetMap(config?, mapInstance?)

function resetMap(
  config?: { center?: Coordinate; isClearAll?: boolean },
  mapInstance?: CustomMap
): void
// 未指定 center 时使用目标地图创建时的中心;isClearAll 保留兼容,仍清理全部业务图层。

清除所有非底图图层(内部调用 removeAllLayers)并复位缩放与中心。

resetZoomAndCenter(center?, mapInstance?)

function resetZoomAndCenter(center?: Coordinate, mapInstance?: CustomMap): void // 默认目标地图初始中心

动画(1s)复位到默认缩放 9.4 与指定中心。

instance

let instance: CustomMap // 导出变量;未创建时为 undefined

二、底图与图层名管理(layer/base-layer.ts)

createBaseLayer(layerName?)

杭州规自瓦片底图(EPSG:4326,XYZ,5 路负载)。

function createBaseLayer(layerName?: HzsyType): Tile
// HzsyType = 'hzsyraster' | 'hzsyvector' | 'hzsyvector_dark'(默认 'hzsyvector_dark')

图层附带属性 { _isBaseLayer: true, _layerName: layerName },zIndex: -5。

const base = createBaseLayer('hzsyvector')
map.addLayer(base)

createMapWorldLayer(layerName, tk?, config?)

全国天地图经纬度 XYZ 图层,支持在线服务和下载器生成的本地栅格。在线仍访问官方 WMTS GetTile 接口,将 {z}、{x}、{y} 填入请求参数;前端不使用 WMTS / WMTSTileGrid,也不访问 DataServer。

function createMapWorldLayer(
  layerName: WorldLayerType,
  tk?: string,
  config?: WorldLayerLocalOptions
): Tile<XYZ>
// WorldLayerType = 'vec_c' | 'cva_c' | 'img_c' | 'cia_c' | 'ter_c' | 'cta_c' | 'ibo_c'

interface WorldLayerLocalOptions {
  url: string // 必须包含 {z}、{x}、{y},扩展名与实际文件一致
}
参数类型默认说明
layerNameWorldLayerType—形如 img_c,对应 EPSG:4326 经纬度矩阵
tkstring内置默认令牌原有第二参数,在线访问的 Key;本地模式可传 undefined
configWorldLayerLocalOptions无新增第三参数,提供本地或自托管的 XYZ 地址,不附加 Key
// 现有在线调用方式保留
map.addLayer(createMapWorldLayer('img_c', '你的天地图令牌'))

// 本地影像与注记,无需 TK;先将下载目录通过 HTTP 提供在 /tiles 下
map.addLayer(
  createMapWorldLayer('img_c', undefined, {
    url: '/tiles/img_c/{z}/{x}/{y}.jpg'
  })
)
map.addLayer(
  createMapWorldLayer('cia_c', undefined, {
    url: '/tiles/cia_c/{z}/{x}/{y}.png'
  })
)

瓦片网格按 [-180, 90] 为原点,256 像素瓦片,第 z 级分辨率为 360 / (256 * 2 ** z);支持第 1~19 级,目录层级直接对应 {z},不再使用 WMTS 的矩阵索引偏移。这里只接收 *_c 经纬度栅格,不能传入 *_w 或 VTS 数据。行号从北向南增加,不使用 {-y}。

本地模式关闭横向世界重复,不自动回退在线服务;仅已下载的区域与层级有图,未下载部分会返回 404。地址可以是同源路径或静态服务器的 HTTP(S) URL;跨域服务器需允许 CORS。在线模式保持世界重复。图层保留 _isBaseLayer: true、_layerName、zIndex: -10,继续支持按名称查询与非底图清理。

底层 getSource() 现在返回 XYZ;直接使用 WMTS 专有方法(如 getMatrixSet()、getRequestEncoding())或旧网格索引的调用方需要迁移。普通 createMapWorldLayer(layerName, tk)、挂载及图层管理调用无需调整。回归见 test/world-layer.test.ts 与示例包 map-utils-world-layer.spec.ts。

createZheJiangLayer(layerName?, tk?)

浙江省天地图 XYZ 图层。

function createZheJiangLayer(layerName?: ZheJiangType, tk?: string): Tile
// ZheJiangType = 'emap' | 'emap_lab' | 'imgmap' | 'imgmap_lab'(默认 'emap')

getLayerByName(name, mapInstance?)

function getLayerByName(
  name: string,
  mapInstance?: CustomMap
): Layer<Source> | undefined

按图层 _layerName 属性或 className 查找首个匹配图层。

removeLayerByName(name, mapInstance?)

function removeLayerByName(name: string, mapInstance?: CustomMap): void

查找所有匹配图层,逐一释放其数据源、从地图移除并执行 dispose。

changeLayersByNames(activeNames, allLayerNames)

function changeLayersByNames(
  activeNames: string[],
  allLayerNames: string[]
): void

在 allLayerNames 范围内,按 activeNames 是否包含来逐一 setVisible,实现批量显隐切换。

changeLayersByNames(['poi-layer'], ['poi-layer', 'line-layer', 'face-layer'])

三、矢量图层(layer/vector-layer.ts)

createBusinessLayer(dataList, config, mapInstance?)

坐标转换复制坐标树,不修改传入数组,也支持 Vue 响应式数组。二维闭合单环自动包装为 Polygon 的环数组;带洞面与 MultiPolygon 保留原层级。

核心矢量图层构造函数:将业务数据组装为 GeoJSON(自动推断线/面几何类型),生成默认/激活样式并写入要素属性,可选开启点击交互。

VectorConfig 复用 Omit<VectorLayerOptions, 'source' | 'map'>,支持 visible、opacity、zIndex、extent、缩放/分辨率范围、declutter、renderBuffer、properties 等原生配置。默认 zIndex: 5,显式 className 优先于 layerName 派生的类名;业务图层名仍保存在 _layerName。内部数据源不可覆盖,图层需由调用方 map.addLayer() 挂载;openClick 开启时选择交互已加入所属地图。

原生 style 按 OpenLayers 规则作为图层级样式:业务 Feature 自身的默认/激活样式仍优先,追加数据也遵循此规则。使用 strokeColor、fillColor 和 active* 配置业务样式;properties 中的同名内部标记会由库覆盖。

function createBusinessLayer(
  dataList: MassData[],
  config: VectorConfig,
  mapInstance?: CustomMap
): VectorLayer
config 关键字段类型说明
keystring数据中存放坐标数组的字段名
isLineboolean是否线图层(影响是否生成填充样式)
layerNamestring?业务图层名,也是未配置 className 时的默认类名
visible / opacity / zIndexboolean? / number? / number?原生可见性、整体透明度和层级,默认 true / 1 / 5
openClickboolean?是否创建单击选择交互
colorKey / colorMapstring? / Map<string,string>?按数据字段值映射颜色
fillColor / strokeColorstring?填充 / 描边色
fillOpacity / strokeWeightnumber?填充透明度 / 线宽
activeFillColor / activeStrokeColor / activeStrokeWeight…激活态样式
crsFormCRSTypes?源坐标系(默认 WGS84)
isAutoFitboolean?选中后是否自动居中

图层附带属性:_isCustom、_isVectorLayer、_isAutoFit、_vectorConfig、_layerName、_dataList、_uuidSet。

const layer = createBusinessLayer(dataList, {
  key: 'coordinates',
  isLine: false,
  openClick: true,
  layerName: 'area',
  visible: true,
  opacity: 0.8,
  zIndex: 8,
  colorKey: 'level',
  colorMap: new Map([
    ['1', '#f00'],
    ['2', '#0f0']
  ]),
  fillOpacity: 0.3,
  strokeWeight: 2
})
map.addLayer(layer)

createVectorLineLayer(dataList?, config, mapInstance?) / createVectorFaceLayer(dataList?, config, mapInstance?)

createBusinessLayer 的便捷包装,分别固定 isLine: true / isLine: false。

function createVectorLineLayer(
  dataList?: MassData[],
  config: VectorConfig,
  mapInstance?: CustomMap
): VectorLayer
function createVectorFaceLayer(
  dataList?: MassData[],
  config: VectorConfig,
  mapInstance?: CustomMap
): VectorLayer

appendFeaturesByLayer(targetLayer, dataList, crsForm?)

向已有矢量图层追加要素(增量更新其 _uuidSet 与 _dataList),复用图层 _vectorConfig 生成样式;未传 crsForm 时继承图层配置的源坐标系。

function appendFeaturesByLayer(
  targetLayer: VectorLayer,
  dataList: MassData[],
  crsForm?: CRSTypes
): void

四、海量点 MassMarks(layer/mass-marks.ts)

buildMassMarksStyles(getImgUrlFn)

高阶函数:先注入「图标地址生成器工厂」,再按 IconConfig 生成「默认 + 激活」两段样式数组(长度 statusNum * 2)。

function buildMassMarksStyles(
  getImgUrlFn: GetImgUrlFn
): (iconConfig: IconConfig) => Style[]

type GetImgUrl = (name: string, ext?: string) => string
type GetImgUrlFn = (baseUrl: string) => GetImgUrl

图标地址按 ${markerIcon}-${i} / ${markerIcon}-${i}-active(后缀 .webp)拼接。

const buildStyles = buildMassMarksStyles(
  (baseUrl) => (name, ext) => `${baseUrl}/${name}${ext}`
)
const styles = buildStyles({
  baseUrl: '/icons',
  markerIcon: 'alarm',
  markerSize: [32, 32],
  statusNum: 5
})

createMassMarksLayer(dataList, styleList?, config?)

function createMassMarksLayer(
  dataList: MassData[],
  styleList?: StyleLike[],
  config?: Partial<LayerConfig>
): VectorLayer

按 item.style 索引在 styleList 中取样式创建要素图层,默认 zIndex: 2000。可选 config 透传原生图层选项和 layerName,显式 className 优先;内部数据源和地图挂载不可由配置覆盖,返回图层需手动挂载。图层附带 _isMassMarksLayer、_layerName、_statusNum、_dataList、_uuidSet 等属性。

创建与追加均先复制并数值化 lnglat;仅显式传入 crsForm 时转换为 WGS84,省略时不调用坐标转换。输入坐标及业务属性保持不变。

addMassMarksLayer(getImgUrlFn)

高阶函数,返回的方法会生成样式、创建海量点图层、按 hasLine / hasFace 附加伴生矢量图层,并自动加入地图。

IconConfig 复用 Omit<VectorLayerOptions, 'source' | 'map'>,顶层 visible、opacity、zIndex、className、缩放范围等原生选项仅作用于海量点图层;伴生线面仍通过 vectorConfig 独立配置。默认层级为 2000,layerName 同时用作未指定 className 时的类名。图层级 style 遵循 OpenLayers 优先级,点要素由样式表设置的默认/激活样式仍优先,追加/替换保持此行为。properties 中的内部标记由库覆盖。

function addMassMarksLayer(
  getImgUrlFn: GetImgUrlFn
): (
  dataList?: MassData[],
  iconConfig: IconConfig,
  openClick?: boolean,
  mapInstance?: CustomMap
) => VectorLayer
iconConfig 字段类型说明
baseUrlstring图标根地址
markerIconstring图标名前缀
markerSize[number, number]图标尺寸
statusNumnumber?状态数(默认 5)
activeScalenumber?激活放大倍数(默认 1.3)
hasLine / hasFaceboolean?是否附带线 / 面图层
isDynamicboolean?是否动态图层(选中派发 dynamic-marker)
isAutoFitboolean?选中是否自动居中
layerNamestring?图层名
offset[number, number]?偏移
marksStylesStyle[]?直接指定样式(跳过自动生成)
visible / opacity / zIndexboolean? / number? / number?点图层原生配置,默认 true / 1 / 2000
vectorConfigVectorConfig?伴生线/面图层配置覆盖
const add = addMassMarksLayer((baseUrl) => (n, e) => `${baseUrl}/${n}${e}`)
const layer = add(dataList, {
  baseUrl: '/icons',
  markerIcon: 'alarm',
  markerSize: [32, 32],
  statusNum: 5,
  opacity: 0.8,
  zIndex: 2100,
  hasLine: true
})

内部 _dataList 和 _uuidSet 只读使用:追加时原地扩展,不再每批复制全部历史;clear: true 使用新集合替换。调用方传入的数组不被追加修改;需要快照时自行复制。直接使用 createMassMarksLayer(data, styles) 创建后,追加仍继承同一套默认与激活样式。

addFeaturesByLayer(targetLayer, dataList, config)

向海量点图层增量添加要素(复用图层 _massMarksStyles)。

function addFeaturesByLayer(
  targetLayer: VectorLayer,
  dataList: MassData[],
  config: { clear: boolean }
): void

config.clear 为 true 时先清空数据源再添加。

updateMarksLayersByStatus(checkedStatusList?, mapInstance?)

function updateMarksLayersByStatus(
  checkedStatusList?: string[],
  mapInstance?: CustomMap
): MassData[]

遍历所有海量点图层:status 不在勾选列表中的要素设为隐藏样式,否则恢复默认样式。返回当前保留显示的数据数组。

changeFeaturesVisibleByIds(layerName, visibleIdSet, mapInstance?)

function changeFeaturesVisibleByIds(
  layerName: string,
  visibleIdSet: Set<string>,
  mapInstance?: CustomMap
): void

遍历所有同名海量点图层,隐藏业务 id 不在集合中的要素,集合中的要素恢复 unActiveStyle。此方法只修改样式,不移除要素或清除已有选择。


五、聚合图层(layer/cluster-layer.ts)

交互演示:启动 packages/example,打开 /map-utils-cluster,可在天地图 VTS 底图上配置聚合样式、切换点击模式并查看返回数据。

createClusterLayer(dataList, config, mapInstance?)

聚合与海量点统一使用 MassData。业务侧传入 id、status、name、style、lnglat、rawData;需要按 UID 激活时还应传入稳定的 _uuid。原 ClusterData 已移除,调用方直接改用 MassData,不保留兼容别名。

聚合内部复用海量点的 createFeatures:复制并数值化数字或字符串二维坐标,仅显式传入 crsForm 时转换为 WGS84,省略时不调用坐标转换;不修改输入数据。单点使用业务字段 style 作为 singleStyles 索引,默认与激活段保持一致。

function createClusterLayer(
  dataList: MassData[],
  config: ClusterConfig,
  mapInstance?: CustomMap
): VectorLayer

type MassData = {
  _uuid?: string
  id: string
  name: string
  style: number
  status: string
  lnglat: [number, number] | [string, string]
  crsForm?: CRSTypes
  width?: number
  height?: number
  rawData: Record<string, any>
}
type ClusterConfig = {
  distance: number
  minDistance: number
  singleStyles: Style[]
  clusterStyle?: Style
  layerName?: string
  openClick?: boolean
  clickMode?: 'single' | 'all' | 'zoom' | 'spiderfy'
  computedRadius?: (size: number) => number
  buildStyle?: (feature: FeatureLike) => Style
}

buildStyle 仍优先接管所有样式。未传 buildStyle 时,单点按业务 style 字段使用 singleStyles;多点优先使用 clusterStyle,未配置则使用内置的动态半径、色相(hsla)和数量文字。

clusterStyle 是一个 OpenLayers Style 模板,仅用于多点聚合。通过 Icon 配置背景图片及大小,通过 Text 配置数字颜色、字号、字体和偏移位置;库自动填入聚合数量,无需回调或单点判断。模板中的 text 内容会被数量替换,未配置 Text 时补充原生默认文字样式。库按数量缓存模板副本,不修改传入的样式;图标大小由模板决定,不应用 computedRadius。

const cluster = createClusterLayer(dataList, {
  distance: 80,
  minDistance: 20,
  singleStyles: [singlePointStyle],
  openClick: true
})
map.addLayer(cluster)

自定义聚合气泡图标、数字颜色、字号和位置:

import { Fill, Icon, Style, Text } from 'ol/style.js'

const cluster = createClusterLayer(dataList, {
  distance: 80,
  minDistance: 20,
  singleStyles: [singlePointStyle, activeSinglePointStyle],
  clusterStyle: new Style({
    image: new Icon({
      src: '/images/cluster.png',
      width: 42,
      height: 48,
      anchor: [0.5, 1] // 底部居中,尖端对齐点坐标
    }),
    text: new Text({
      font: 'bold 16px sans-serif',
      fill: new Fill({ color: '#fff' }),
      textAlign: 'center',
      textBaseline: 'middle',
      offsetX: 0,
      offsetY: -48 / 2 - 4 // 从底部锚点移至图标中心,再向上微调 4px
    })
  }),
  openClick: true
})
map.addLayer(cluster)

聚合点击数据

开启 openClick: true 后,通过 clickMode 决定 selected-feature 事件的数据形状:

  • single(默认):保持原行为,仅点击聚合内只有一个点时返回该点的属性对象;点击多点聚合不派发事件。
  • all:返回点击位置聚合内全部点的属性数组;只有一个点时也返回长度为 1 的数组。数组元素与原单点事件相同,含 _uuid、name、lnglat、rawData 等属性,不是 OpenLayers Feature 实例;嵌套业务数据仍应只读使用。
  • zoom:点击多点聚合时,用 300ms 动画适配这组点的范围,让聚合随放大自然拆开,不派发多点数据;点击单点仍返回单个属性对象。使用所属地图的最大缩放级别作为上限,同坐标点或达到最大级别后仍可能聚合。
  • spiderfy:未达到最大缩放级别时与 zoom 一样放大;达到最大级别后仍有多点聚合,再次点击才围绕聚合中心圆形展开,并添加连接线。点击展开的点返回原始单点属性对象并收起,原始坐标和业务数据不变,其他聚合组不变。点击空白、移动/缩放/旋转地图、隐藏或移除图层及父图层组、变更数据源、移除交互及销毁地图时清理展开层和监听。

需要“先放大,再展开重合点”时配置 openClick: true, clickMode: 'spiderfy'。展开复用 singleStyles,不新增图标配置;当前为单圈布局,大量点或靠近视口边缘时可能超出可视范围,未实现螺旋或列表布局。

const cluster = createClusterLayer(dataList, {
  distance: 80,
  minDistance: 20,
  singleStyles,
  openClick: true,
  clickMode: 'all'
})
map.addLayer(cluster)

emitter.on('selected-feature', (data) => {
  // 事件总线同时接收普通图层的单个对象。
  const points = Array.isArray(data) ? data : [data]
  console.log(points.map((point) => point.rawData))
})

仅配置 clickMode 不会自动开启点击。该配置不影响普通矢量图层;single / all 不自动缩放,zoom / spiderfy 不永久关闭聚合,缩小地图后仍会重新聚合。

六、交互与选中(interaction/select.ts)

createSelectByLayer(vectorLayer, mapInstance?)

为指定图层创建 ol/interaction/Select(单击、multi: false、hitTolerance: 10),命中要素时切换激活样式并派发 selected-feature 事件;聚合图层按创建时的 clickMode 返回单点对象、全部点属性数组、放大多点聚合,或在最大级别再次点击时圆形展开。交互会写入图层属性 _selectInteraction。

function createSelectByLayer(
  vectorLayer: VectorLayer,
  mapInstance?: CustomMap
): Select

setFeaturesActive(selector, active?, mapInstance?)

按业务 ID 集合或筛选函数批量切换普通业务矢量图层(包括 MassMarks 与伴生线面)的要素样式,以及聚合图层内原始点的高亮。省略地图参数时使用已创建的单例;多地图请显式传入目标地图。

import { setFeaturesActive } from 'map-ol-utils'

setFeaturesActive(['device-1', 'device-2']) // 顶层业务 id,严格区分字符串与数字
setFeaturesActive(new Set(['device-1', 'device-2']), false) // 恢复默认样式
setFeaturesActive((feature) => feature.get('status') === 'alarm')
setFeaturesActive((feature) => feature.get('_uuid') === 'uuid-1', true, map)
setFeaturesActive(['cluster-point-1', 'cluster-point-2'], true, map) // 聚合内原始点
setFeaturesActive(() => true, false, map) // 恢复目标地图内所有适用要素

selector 接受只读 Array<string | number>、ReadonlySet<string | number> 或 (feature: Feature) => boolean,active 默认 true,返回 void。集合匹配由 MassData.id 写入的顶层 id,不读取 _uuid、rawData.id 或 OpenLayers 的 getId();按其他字段匹配请使用筛选函数。仅更新命中的要素:激活使用 activeStyle,取消使用 unActiveStyle;缺少目标样式时保持原样,空集合不改变任何要素。同一业务 ID 在多个适用图层中出现时会全部命中。筛选函数应只读取属性。

聚合图层按内层原始 Feature 匹配,不要求 _uuid 或 openClick。多点聚合保留原气泡与数量,在命中点的原坐标叠加 singleStyles 后半组的激活样式;拆成单点时直接显示激活样式,取消后恢复图层原有样式(包括 buildStyle)。不修改原始坐标、原始 Feature 样式或业务传入的样式对象,不增加临时图层,也不触发重新聚合。高亮随缩放、移动和原始点几何更新保留;替换成新的 Feature 不继承旧高亮。完全同坐标的高亮点会重叠,spiderfy 展开后在各自展开位置显示,展开期间也可批量取消或激活。

渲染缓冲按创建时的聚合距离预留成员与聚合中心的偏移,保证聚合中心在视口外时仍可显示视口内的高亮点;需要动态增大聚合距离时请用新配置重建图层。

此方法只切换外观,不写入选择集合、不触发 selected-feature、不移动视野;点击仍遵循图层的 clickMode。已有点击交互或 activeFeatureByUid() 的选择高亮独立管理,批量取消不清除选择;取消批量高亮请调用本方法并传入 false。Marker 图层与没有 _isVectorLayer 标记的自定义图层不在处理范围。回归见 test/cluster-batch-active.test.ts 与示例包 map-utils-cluster-batch-active.spec.ts。

activeFeatureByUid(uid, mapInstance?)

function activeFeatureByUid(uid: string, mapInstance?: CustomMap): void

在已挂载且绑定选择交互(通常配置 openClick: true)的矢量图层中,按原始要素的 _uuid 激活首个匹配项。普通图层通过 _uuidSet 定位;聚合图层直接查找 Cluster 内层数据源,不要求 _uuidSet,也不要求该点已经从聚合组中拆开。未命中时不改变当前选择。

聚合点在原始坐标创建一个临时显示副本,使用该点的 activeStyle(由 singleStyles 后半组提供),只高亮目标点;不移动视野、不改变原始数据、坐标或聚合数量,也不触发整组的放大或展开行为。selected-feature 返回目标原始点属性,clickMode: 'all' 时为仅含该点的数组。重复激活同一点不重复通知,同一图层切换 UID 时替换旧高亮。

activeFeatureByUid('point-a', map) // 聚合组中的 point-a 单独显示为激活态
resetActiveFeature(map) // 清理临时高亮与选择

聚合高亮要求图层可见且选择交互启用;resetActiveVector()、选择集合清空、点击其他位置、视图变化、图层隐藏/移除、数据变化、交互移除及地图销毁都会清理临时显示。高亮仅是当前交互状态,视图或数据更新后需要时可重新调用。原有鼠标点击仍遵循 clickMode。

resetActiveFeature(mapInstance?) / resetActiveVector(mapInstance?)

function resetActiveFeature(mapInstance?: CustomMap): void // 清空 overlay + 触发各矢量图层 deselect
function resetActiveVector(mapInstance?: CustomMap): void // 清空 overlay + 清空各图层选择交互的要素集合

七、覆盖物与视野(overlay/overlay.ts、map/view-fit.ts)

createOverlay(config?)

function createOverlay(config?: import('ol/Overlay.js').Options): Overlay
// 默认:{ id: 'popup-container', positioning: 'top-center' }

未显式传 element 时,按 config.id 取 document.getElementById(id) 作为容器。

removeOverlays(key?, mapInstance?) / removeAllLayers(mapInstance?)

function removeOverlays(key?: string, mapInstance?: CustomMap): void // 移除带指定属性键的 overlay(默认按 'position')
function removeAllLayers(mapInstance?: CustomMap): void // 移除并释放所有非底图(无 _isBaseLayer)图层 + overlay

createAnimationMarker(imgUrl, data, mapInstance?)

function createAnimationMarker(
  imgUrl: string,
  data: MassData,
  mapInstance?: CustomMap
): void

以 data.lnglat(经 data.crsForm → WGS84 转换)创建动画标注覆盖物,先移除目标地图已有动画标注;removeAnimationMarker(mapInstance?) 可单独清理该地图的动画标注。data 中读取 width / height / lnglat / crsForm。

setFitViewByCoords / setFitViewByGeom / setFitViewByLayer / setFitViewByLayers

坐标数组为空时直接返回,不访问地图、不改变视野。

function setFitViewByCoords(
  coords: Coordinate[],
  mapInstance?: CustomMap,
  fitOptions?: FitOptions | AnimationOptions
): void // 单点时 animate 到 zoom 14,多点时 fit 包围盒

function setFitViewByGeom(
  geom: SimpleGeometry,
  mapInstance?: CustomMap,
  fitOptions?: FitOptions
): void

function setFitViewByLayer(
  layer: VectorLayer,
  mapInstance?: CustomMap,
  fitOptions?: FitOptions
): void // 按图层 source 的 extent 自适应

function setFitViewByLayers(
  layers: readonly VectorLayer[],
  mapInstance?: CustomMap,
  fitOptions?: FitOptions
): void // 合并多个矢量图层的 source 范围,一次适配

范围适配默认 padding: [50,50,50,50]、duration: 1000,可由 fitOptions 覆盖。

setFitViewByLayers 跳过无数据源、空数据源以及空或非有限范围;没有有效范围时直接返回,不访问地图。调用方应在数据加载完成后调用,图层坐标必须与 View 投影一致。按显式传入的图层计算,不筛选可见性、不递归图层组,也不会监听后续数据变化。

适配仍遵守 View 的缩放约束。createMap / createMapInstance 默认 minZoom: 9.4;大范围数据需要在创建地图时调低 minZoom(例如 0),否则可能无法完整显示。少量点可通过 fitOptions.maxZoom 限制放大程度。

setFitViewByCoords([
  [120.1, 30.2],
  [120.3, 30.4]
])
setFitViewByLayer(layer, undefined, { duration: 500 })
setFitViewByLayers([pointLayer, lineLayer, polygonLayer], map, {
  padding: [50, 50, 50, 300], // 为左侧面板留白
  maxZoom: 16,
  duration: 500
})

getLayersDataList(mapInstance?)

function getLayersDataList(mapInstance?: CustomMap): any[]

汇总所有图层 _dataList 属性并 flat(2),返回当前地图上的全部业务数据。


八、WMTS(layer/ogc-utils.ts)

createWmtsLayer(serverUrl)

柯里化函数:先传 GeoServer 服务地址,返回的方法按 WmtsSourceOptions 解析 GetCapabilities 并构建图层。仅成功响应会进入 capabilities 缓存;HTTP 非成功状态直接抛错,后续调用仍可重试。

function createWmtsLayer(
  serverUrl: string
): (config: WmtsSourceOptions) => Promise<TileLayer>

type WmtsSourceOptions = {
  layerName: string // 形如 'workspace:datastore'
  style?: string
  format?: string // 默认 'image/png'
  version?: string // 默认 '1.1.0'
  matrixSet?: string // 默认 'EPSG:4326'
}
const build = createWmtsLayer('https://geoserver.example.com/geoserver')
const layer = await build({ layerName: 'ws:store', matrixSet: 'EPSG:4326' })
map.addLayer(layer)

九、事件总线(emitter)

import { emitter } from 'map-ol-utils' // mitt 实例
事件名载荷触发时机
selected-feature要素属性对象;聚合图层 clickMode: 'all' 时为属性数组要素被选中(createSelectByLayer 内触发)
dynamic-marker动画标注配置选中动态图层(_isDynamic)要素 1s 后

十、扩展能力

以下 API 补充天地图组合、标记、绘制与 GeoJSON 等能力;createMapInstance、createOverlay 保持原签名;disposeMap 及下述地图相关工具增加可选目标地图参数,省略时保持原单例行为。disposeInstance() 直接复用 disposeMap() 的完整清理语义。

天地图组合与栅格处理

const { baseImgLayer, baseCiaLayer, baseVecLayer, baseCvaLayer } =
  baseLayers('你的天地图令牌')

const recoloredLayer = changeLayer(
  baseImgLayer,
  [255, 255, 255],
  [0.05, 0.55, 0.05]
)
  • baseLayers(tk):创建影像、影像注记、矢量、矢量注记四个 XYZ 图层。
  • changeSource(layer, rgbColorArray, ratioArray, options?):创建栅格颜色转换数据源;options.threads 可指定线程数。
  • changeLayer(layer, rgbColorArray, ratioArray, options?):把转换数据源包装为 VectorImageLayer;支持相同的线程配置。

options 类型为 { threads?: number }。未指定时使用浏览器硬件并发数与 4 的较小值(无法读取时为 2),且至少使用 1 个线程。

标记与选择

const markerLayer = createMarkersLayer(markerData, {
  layerName: 'device-layer',
  zIndex: 10,
  opacity: 0.8,
  minZoom: 5,
  declutter: true
})
map.addLayer(markerLayer)

const select = createLayerSelectByNames(['device-layer'], handleSelected)
map.addInteraction(select)
  • createText(item) / createIconStyle(item, active?):创建标记文本和图标样式。
  • createMarker(item, config) / createMarkersLayer(dataList, config):创建单个标记或标记图层。
  • createElasticMarkerLayer(dataList, config, mapInstance?):在 zoom < 11、11 ≤ zoom < 14、zoom ≥ 14 三档间切换 img、smallImg、largeImg,仅跨档刷新样式;选中时保留激活图标,取消选择按所属地图的当前档位恢复。通过库的生命周期方法移除时自动解绑缩放监听。
  • changeUnselectStyle(featureCollection, mapInstance?):清空选择集合并恢复未选中样式。
  • createLayerSelectByNames(layerNames, selectedHandler?, unSelectHandler?):按多个图层名选择标记。
  • createMoveMarkerLayer(dataItem, config, mapInstance?):创建单标记图层并加入指定地图,省略地图参数时使用单例。

绘制、GeoJSON 与视野

  • createDrawInteraction(type, vectorLayer, openTip?):创建绘制交互。
  • createRouteController(config?):自动获取地图并挂载航线资源,统一模式、状态订阅、输入与生命周期,默认空闲。
  • getDrawStyle(feature, drawStyle?):为线和面绘制追加长度/面积标签。
  • createGeoJsonLayer(geoJson, style, config?):直接从 GeoJSON 创建矢量图层。
  • flyToAnimate(center, zoom?, mapInstance?):使用两段缩放动画飞到目标点。

相关类型包括 LayerConfig、DataItemConfig、ElasticDataItemConfig。

LayerConfig 复用 ol/layer/Vector 的 Options,排除由封装管理的 source、map,并要求业务标识 layerName。Marker、弹性 Marker、移动 Marker 和 GeoJSON 图层支持透传 opacity、extent、minZoom、maxZoom、declutter、renderBuffer 等原生参数;显式 className 优先于 layerName 派生的默认类名。普通与弹性 Marker 默认 visible: true、zIndex: 10,移动 Marker 保留 OpenLayers 默认值。

Marker 的 Feature 自有图标样式优先于图层 style。GeoJSON 保留原有合并顺序:config.style 覆盖独立 style 参数(包括 null);未提供时沿用独立参数及原有默认样式。

自定义 className 时,业务 layerName 仍保存在 _layerName,供 getLayerByName / removeLayerByName 查询和清理。createLayerSelectByNames / changeLayersByNames 仍按 CSS className 匹配,调用这两个接口时需传入实际类名。

航线控制器

先创建地图,再创建航线。map 可选,默认使用 createMapInstance() 创建的实例;多地图时显式传入所属地图。库自动创建并挂载航线图层和主交互,不需要手动添加交互。初始为 idle,不拦截地图操作。航点与操控仅支持 EPSG:4326 经纬度。

import { createMapInstance, createRouteController } from 'map-ol-utils'

const map = createMapInstance({ center: [120.1551, 30.2741], zoom: 17 })
map.setTarget('map')
const route = createRouteController({
  input: { keyboard: true }, // 可选:地图或面板聚焦时响应快捷键
  speed: 10,
  turnSpeed: 60,
  verticalSpeed: 1,
  edit: { mode: 'snap', pixelTolerance: 10 },
  onWaypointAdd(waypoint) {
    console.log(
      '新增航点',
      waypoint.uuid,
      waypoint.indexNo,
      waypoint.longitude,
      waypoint.latitude
    )
  },
  onBeforeWaypointRemove(waypoint) {
    return window.confirm(`删除航点 ${waypoint.indexNo}?`) // false 阻止删除
  },
  onWaypointRemove(waypoint) {
    console.log('已删除普通航点', waypoint.uuid, waypoint.indexNo)
  }
})

// 业务自己的按钮直接调用:
route.setMode('takeoff') // 下一次地图单击确认起飞点,完成后回到 idle
// 确认起飞点后:
// route.setMode('draw')    // 地图点击追加航点
// route.setMode('control') // 从最后记录点开始操控
// route.setFlightConfig({ speed: 15 }) // 可在操控中更新水平速度(米/秒)
// route.setMode('edit')    // 编辑已有航点
// route.selectWaypoint('航点 UUID') // 编辑模式按 UUID 选中普通航点
// route.setMode('idle')    // 停止操作,保留航线

const unsubscribe = route.subscribe((state) => {
  console.log(state.mode, state.waypointCount, state.aircraft)
})
// const data = route.exportWaypoints() // 保存请求由业务负责
// route.clear() // 清空后复用同一实例,配置和输入保留
// unsubscribe() // 可提前取消;destroy 会清理所有订阅
// route.destroy() // 移除本航线的图层、交互和输入监听,不销毁地图

模式与生命周期

模式行为
idle保留数据,停止当前操作
takeoff下一次地图单击设置或修改起飞点,成功后返回 idle
draw单击追加独立航点并依次连线,没有鼠标跟随线
control实时飞机移动、转向、升降;显式打点才记录航点
edit选择、拖动普通航点,插入、删除和调整高度

draw、control、edit 要求已有起飞点,可通过地图确认或导入非空航点建立。配置和前置校验失败不切换模式;重复进入同一模式不重启飞控、不丢失选择。切换模式由库清理输入、临时飞机、选择及未完成拖动。操控时普通地图点击不追加航点或移动飞机。

clear() 清空航点并回到 idle,保留实例、样式、编辑配置、当前飞行配置和输入配置。destroy() 幂等,永久移除本航线全部资源和订阅;销毁后不能再次切换模式、导入、更新飞行配置或订阅。外部停用、移除交互,或改变地图容器、View 时停止操作并回到 idle;重新启用不会自动恢复上一次操作。航线不支持迁移到其他地图。调用方应先销毁航线,再销毁地图。

本次接口统一移除了旧的绘制开关和多个模式切换方法,所有模式通过 setMode() 管理,所有编辑配置通过 setEditConfig() 更新。

设置或修改起飞点时,地图视口使用同一 SVG 作为鼠标指针,热点位于 SVG 中心像素(11、16,浏览器指针热点采用整数像素),无需启用键盘或配置面板。指针仅作用于地图视口,确认起飞点、退出模式、清空、导入、停用、移除、容器或 View 变化及销毁后恢复原有内联指针与优先级。draw(航点连线)模式使用 crosshair。同一地图多条航线处于起飞或绘制模式时,最近进入的模式决定指针;退出后恢复仍活跃的模式指针,全部退出后恢复原指针。

新增航点回调

创建时传入 onWaypointAdd(waypoint: RouteWaypoint),每次成功新增普通航点(地图点击打点、飞控打点(键盘、面板或 addRouteWaypoint())或编辑插点)后同步调用一次。设置或修改起飞点不触发。编辑插点仅在拖动结束且去重通过后通知;修改起飞点、拖动已有点、调整高度、删除、清空、批量导入、重复位置拒绝与取消插点均不触发。

回调参数与 exportWaypoints() 的单项格式一致,包含 uuid、当前 indexNo、经纬度、高度及业务字段,是新的顶层对象;嵌套对象仍须只读使用。只序列化本次新增点,不导出整条航线。回调内可清空、导入或销毁控制器。此前的同步状态订阅若已移除该点或销毁控制器,则不再通知失效航点。回调抛错向调用方传播,已完成的新增不会回滚。

普通航点删除回调

removeWaypoint(pointOrUuid?, options?: RouteRemoveWaypointOptions) 仅在编辑模式删除普通航点,支持本航线 UUID 字符串或 Feature<Point>,省略第一个参数时删除选中点。UUID 按原值精确匹配,不存在或为空时返回 false,不会回退删除当前选中点。

route.setMode('edit')
route.removeWaypoint('目标航点UUID') // 默认执行删除前拦截
route.removeWaypoint('目标航点UUID', { intercept: false }) // 本次跳过删除前回调
route.removeWaypoint(undefined, { intercept: false }) // 删除选中点,本次跳过拦截

options.intercept 默认启用。设为 false 只跳过 onBeforeWaypointRemove;起飞点、当前航线归属、未提交插点和编辑模式校验仍保留,成功后照常触发 onWaypointRemove。此设置仅影响当前调用。

onBeforeWaypointRemove(waypoint: RouteWaypoint) 在 removeWaypoint() 通过校验后、执行删除前同步调用;onWaypointRemove(waypoint: RouteWaypoint) 在删除成功、点线和编辑状态更新完成后同步调用。两者仅针对普通航点,参数都是航点顶层副本,indexNo 保留各自通知阶段删除前的序号。嵌套业务数据应只读使用。

起飞点不能删除,尝试删除时返回 false,两个回调都不触发。非编辑模式、无参调用时未选中点、其他航线、已删除的点或尚未结束拖动的临时插点,以及清空航线、销毁、导入替换和取消插点也不触发。

删除前回调同步返回 boolean | void:只有返回 false 才阻止删除,返回 true、不返回值或未配置回调都继续删除。被阻止时 removeWaypoint() 返回 false,保留航点、选择和编辑交互,不触发 onWaypointRemove;回调自身造成的修改不会撤销。不支持异步返回,不等待 Promise。回调抛错会中止删除并向调用方传播;若回调清空、导入、销毁或切换模式,当前删除停止并返回 false。删除后回调可清空、导入或销毁;抛错向调用方传播,已完成的删除不回滚。删除过程的同步状态通知若已销毁控制器,则不再调用删除后回调。

状态订阅

getState(): RouteState 返回普通数据快照。subscribe(listener) 立即通知一次初始状态,之后在数据、模式、选择、输入或实时飞机变化时通知,返回取消订阅函数;destroy() 自动清理订阅。

飞控将同一动画帧内的位置、航向和高度更新放在一次事务中,完成后只通知一次完整状态。后续通知中某个订阅回调抛错时,通过 console.error 报告,不中断其他订阅或飞控循环;首次同步回传抛错仍向调用方传播,并取消本次注册。新增、删除等业务回调的异常约定不变。

普通业务按钮、输入框或下拉框切换焦点不会单独触发订阅。只有当前存在库托管的按键或指针动作时,进入表单、后台等清理才通知空动作状态;重复清理不再通知空动作。调用航线接口引起的模式、数据变化仍会正常通知,实时飞行也仍会持续通知;这不是对所有状态的全局去重。

字段说明
mode当前互斥操作模式
hasTakeoff是否已有起飞点
waypointCount航点数,包含起飞点,不包含临时飞机与编辑手柄
totalDistance相邻航点的球面水平距离之和,单位米;少于两个点时为 0,不计高度差、临时飞机或自动返航段
selectedWaypoint选中航点的普通数据,未选中为 null
aircraft操控时返回实时飞机的经纬度、heading、height、speed,其他模式返回末个已记录航点的飞机信息;无航点时为 null
activeControls合并后的键盘、指针动作,用于按钮高亮
editConfig当前中点或吸附配置

实时通知只读取飞机与选中点,不逐帧导出整条航线。快照的数组、配置与航点顶层对象独立;航点嵌套业务对象与导出一样仍保留引用,应作为只读数据使用。业务表单、提示文案、权限与后端保存仍由调用方负责。

设置起飞点后立即通知 aircraft:位置为起飞点,初始航向和高度为 0,speed 为控制器配置速度(默认 10 m/s)。非操控期间跟随末个已记录航点的位置、内部航向和高度;导入时航向取 direction、高度取 height。进入操控后使用实时飞机,退出后恢复末点信息,未打点的移动不保留;清空后为 null。speed 始终为配置速度,不是实时测速,也不使用业务字段 pointSpeed。判断是否正在操控请使用 mode === 'control',不要用 aircraft !== null。

调用 setFlightConfig() 后,aircraft.speed 立即反映最新水平速度,操控与非操控模式一致。有效配置变化同步通知一次订阅,空配置或值未变化不通知;无航点时仍允许配置,aircraft 保持 null。

totalDistance 使用 EPSG:4326 经纬度计算,按航线几何版本缓存,多个订阅者和飞机帧复用计算结果。新增、导入、删除、修改起飞点和编辑完成后,随状态通知更新;清空归零。拖动中主动调用 getState() 会读到当前临时航线长度,取消拖动后恢复。

route.subscribe(({ totalDistance }) => {
  console.log(`航线总长度:${totalDistance.toFixed(1)} 米`)
})

业务面板与输入

操作面板由业务创建、布局并根据状态控制显示。库不创建或删除业务 DOM,负责输入事件、可选激活颜色与生命周期。传入操控区和编辑区的公共容器,无需单独传编辑面板或调用绑定方法;也可以只配置 input.keyboard: true 使用键盘,或省略输入配置只使用地图绘制。

飞控快捷键不依赖鼠标位置或地图焦点:启用 input.keyboard 后,control 模式在地图所属 document 监听按键,焦点位于页面其他非输入区域时仍可操作。地图、按钮或窗口失焦不会清空已按住的动作,也不监听 focusout / blur;document 上的 focusin 仅用于进入输入框、下拉框、可编辑内容或链接时清理动作。页面进入后台、松键、指针释放或取消仍会清理对应输入。窗口失焦后若浏览器未送达 keyup,已按住动作会保留,直到再次按下并松开对应键、页面进入后台或退出模式。

航线输入监听仅在 control / edit 模式启用,退出时解绑并恢复临时添加的 tabindex;其他模式仍校验容器 ID。飞控快捷键启用时 keydown 绑定 document,编辑模式仍限定地图和业务容器;跳过已被阻止默认行为的事件,避免嵌套容器重复执行。普通业务按钮上的 WASD 等快捷键可用于飞控,Space / Enter 保留按钮原生行为。关闭快捷键时,容器内按钮仍支持 Enter / Space。keyup 保留 document 捕获监听;未配置 container 时不绑定指针事件。输入期间的松键、指针释放与页面后台清理仍保留。

const route = createRouteController({
  input: {
    keyboard: true,
    container: 'route-controls',
    activeStyle: {
      backgroundColor: '#16a34a',
      borderColor: '#22c55e',
      color: '#fff'
    }
  }
})
<div id="route-controls">
  <button type="button" data-route-control="forward">前进</button>
  <button type="button" data-route-control="ascend">升高</button>
  <button type="button" data-route-control="waypoint">打点</button>
</div>

input.container 可传容器 ID(不带 #)或 HTMLElement。ID 优先在所属地图容器的 document 中查找,地图尚未挂载时使用当前 document;找不到时明确报错,创建失败会回收本次图层与交互。容器必须已创建,Vue 中在 onMounted() 后初始化,无需额外保存面板 ref。事件代理允许内部按钮动态增删。键盘 W/S/A/D 移动、Q/E 转向、Z/C 升降、空格打点;操控模式按住动作持续执行,编辑模式只处理升降,每次 1 米。高度最低 0。键盘和指针分别记录并合并,松开一个来源不会清除其他来源。输入框、选择框、可编辑内容和链接不响应快捷键;进入表单、后台、指针取消及模式切换清空动作,销毁时解绑监听和释放捕获,保留业务容器。显式启用键盘时,进入操作模式会聚焦地图容器;库临时添加的 tabindex 在解绑时恢复。未配置输入时,可直接使用 setRouteControls() 与 addRouteWaypoint() 接入其他设备。

input.activeStyle 可选配置按钮激活时的 backgroundColor、borderColor、color,值使用 CSS 颜色字符串。库根据合并后的键盘与指针动作,为容器内相同 data-route-control 的可用按钮临时应用内联颜色(!important);释放全部输入来源、进入表单、页面后台、切换模式或销毁时恢复原有颜色及优先级,不影响其他样式。不传或省略的颜色属性仍由业务 CSS 决定。该配置只处理库托管的输入高亮,不创建面板,也不替代 activeControls 状态订阅或业务无障碍属性。

按钮激活期间如需动态调整其他外观,优先切换业务 CSS class,避免新增或重写包含 var() 的内联简写声明;这些声明无法按颜色子属性可靠合并。激活前已有的 CSS 变量简写可正常恢复。

按 UUID 选中航点

selectWaypoint(uuid: string): boolean 在 edit 模式下选中当前航线的普通航点,自动恢复旧航点样式、应用新航点激活样式,并同步通知一次 subscribe();通知中的 selectedWaypoint 是最终选中航点,不会先通知空选择。getSelectedWaypoint() 同步返回对应 Feature。

import { Circle, Fill, Style } from 'ol/style.js'
import { createRouteController } from 'map-ol-utils'

const route = createRouteController({
  activePointStyle: new Style({
    image: new Circle({ radius: 10, fill: new Fill({ color: '#00bfff' }) })
  })
})
// 先通过 importWaypoints() 导入数据或在地图上创建航点。
route.setMode('edit')
route.selectWaypoint('waypoint-1')

成功返回 true;目标已经是唯一选中航点时仍返回 true,不重复通知。Shift 点击产生多个选择时,此方法只保留目标并通知。未知 UUID、起飞点、未完成插点、非编辑模式或销毁后返回 false,保留已有选择。此方法不自动切换模式、不移动视野、不修改航点数据。退出编辑、导入或清空等操作沿用原有选择清理规则。底层样式变更事件中重入调用返回 false,避免破坏原样式恢复;subscribe() 回调中仍可切换选中航点。

activePointStyle 优先于默认激活样式,支持 Style、Style[] 或样式函数,空数组隐藏选中点;未配置时使用内置橙心白边放大圆点。程序选中和鼠标选中共用此配置,取消选择恢复原航点样式。

航线样式与数据约定

默认航线连线颜色为橙色 #FFA01E。普通航点为直径 16 像素的白色外圈与半径 5 像素的橙色圆心,使用圆形样式渲染(半径 6.5、白色描边宽度 3)。未配置 activePointStyle 时,编辑选中航点放大为直径 20 像素的橙心白边圆点,取消选择后恢复原样式;中点和吸附插点手柄默认为白底橙边,可用 midpointStyle 覆盖。默认起飞点使用橙色 #FFA01E 的 23×32 SVG 图标,中心锚点对齐地图坐标,替换原圆点,图标内字形为白色,下方紧贴显示白色“起飞点”文字与深色描边,单独图标与飞机叠加时均保留相同的下方间距。仅在首次渲染时创建图标;起飞点同时为末点时仍叠加飞机样式。传入 takeoffStyle 可覆盖默认图标。

不传 style 时,库提供起飞点、普通点、连线、末点飞机与航向旋转的完整样式。显式传入整体 style 时,未配置的分类继承整体样式;null 隐藏整体样式,分类空数组隐藏该分类。takeoffStyle、lineStyle、pointStyle、aircraftStyle 和 midpointStyle 仍可分别覆盖;选中航点独立使用 activePointStyle,省略时采用默认激活样式。自定义飞机样式自行根据 heading 设置旋转。图层配置继承 LayerConfig 的原生参数,例如 opacity、extent、declutter、renderBuffer,显式 className 优先;layerName 在航线中可选,map 由控制器单独管理。

getLayer() 保留底层数据访问能力,但清空与替换必须使用 clear() 或 importWaypoints(),不要直接清理数据源导致内部状态失配。首点标记 isTakeoff,停止后末点标记 isAircraft;操控期间飞机由临时图层显示,不写入导出数据。航点有稳定 UUID,修改位置不改变标识。所有业务属性保存在独立 extData 中;高度为有限米数,允许负数,点击新增继承末点高度,操控打点使用飞机高度,插点继承前点高度,其他业务配置不自动继承。

同一航线按二维坐标精确去重,拒绝重复点击、打点或起飞点冲突时调用可选 onDuplicate(coordinate),参数为坐标副本;编辑拖动重复则回滚。起飞点不能选中、拖动或删除,只能通过 takeoff 模式修改。起飞点与其他航点冲突时保持等待下一次点击。

点击、飞控与编辑完成时,超出 ±180° 的有限经度会归一化到该范围,并按归一化后的坐标去重;原本范围内的经度保持不变。非有限坐标或超出 ±90° 的纬度不会提交:点击忽略,编辑回滚本次拖动或插点。导入仍严格拒绝越界坐标。拖动期间仍可读取临时位置,保存应等待编辑结束;跨经线归一化保证数据可导出再导入,不拆分跨经线的显示线段。

导入航点

importWaypoints(waypoints: readonly RouteWaypoint[]) 整体替换当前航线,按 indexNo 从小到大连线;相同序号保持输入顺序,不修改传入数组。首点设为起飞点,末点标记为飞机,无需另行点击确认。

import { createRouteController, type RouteWaypoint } from 'map-ol-utils'

const controller = createRouteController({ map })
const points: RouteWaypoint[] = [
  {
    waylineId: 'route-1',
    indexNo: 0,
    pointSpeed: 10,
    gimbalPichAngle: -45,
    direction: 90,
    shootType: 'time',
    supportLightType: 'ir,visable',
    longitude: 120.15,
    latitude: 30.28
  },
  { indexNo: 1, longitude: 120.16, latitude: 30.29 }
]
controller.importWaypoints(points)
controller.setMode('edit') // 导入后可继续编辑
// controller.importWaypoints([]) // 清空,hasTakeoff() 恢复 false
字段要求用途
uuid可选,非空白字符串,同一批导入不能重复航点稳定标识;省略时自动生成 UUID v4,导出带回
indexNo必填,非负整数仅用于导入排序,导出时按连线顺序重新编号;不存入 Feature
longitude、latitude必填,有限数值,范围分别为 ±180、±90EPSG:4326 经纬度,写入 Point 几何
waylineId可选,字符串类型提示,不做运行时校验航线业务标识
height可选,有限数值,允许负数,默认 0航点高度(米),保存到 extData.height
pointSpeed可选,数值类型提示,不做运行时校验速度元数据,不改变实时操控速度
gimbalPichAngle可选,数值类型提示,不做运行时校验云台俯仰角,保留接口原字段拼写
direction可选,有限数值航向角,同时写入显示用 heading,缺省为 0
shootType可选,字符串;枚举由业务定义,不做运行时校验拍照模式
supportLightType可选,字符串;取值由业务定义,不做运行时校验影像类型,保留接口原枚举拼写

经纬度只存入几何,编辑后的最新位置通过 feature.getGeometry().getCoordinates() 获取。waylineId、pointSpeed、gimbalPichAngle、direction、shootType、supportLightType、height 和其他业务属性保存在 Feature 的独立 extData 对象中。uuid 在顶层标识航点,heading、isTakeoff、isAircraft 保留在顶层供控制与样式使用;indexNo 只用于导入排序和导出重新编号,不保存在 Feature 中。新航点的 extData 初始化高度;点击新增继承末点高度,飞控打点取当前飞行器高度,中点插入继承前一航点高度,其他业务配置不继承。

const waypoint = controller.getSelectedWaypoint()
if (waypoint) {
  controller.updateWaypoint(waypoint, { pointSpeed: 15 })
}

航点标识通过 feature.get('uuid') 读取。导入保留已有 uuid,缺省时生成;点击、操控打点和中点插入各自生成新 UUID,拖动、修改起飞点、删除其他点及导出重新编号都不改变已有标识。标识在创建时确定,调用方不应直接改写。自动生成使用 Web Crypto 的 getRandomValues(),支持 HTTP 页面。

RouteWaypoint 通过 [key: string]: unknown 接受额外业务字段,例如 heightMode、reachShoot 或嵌套动作配置。导入除 uuid、indexNo、longitude、latitude 外的所有自有可枚举属性到 extData,仅校验航点结构及内部使用的 height、direction,纯业务字段不做运行时校验或模式映射。导入和导出只浅拷贝顶层,嵌套对象、数组保留引用。同名 heading、isTakeoff、isAircraft 等业务属性也只保存在 extData,不覆盖 Feature 内部标记。导出展开 extData 后,以控制器的 UUID、连续序号和最新坐标覆盖同名值。

业务层读取这些字段通过 extData,更新优先使用 updateWaypoint();导出读取最新 extData 并生成原来的扁平结构。拖动只更新几何,不重算业务属性;修改 extData.direction(包括通过 updateWaypoint())不自动更新样式使用的 heading,需要即时刷新航向显示时由调用方同时设置。

导入先校验整批航点结构、高度和航向;这些字段非法或经纬度重复时抛错,原航线、编辑选择和操控状态保持不变。成功后停止操控、取消未完成拖动、清除旧选择;非空导入保留编辑模式并重建手柄,其余模式与空数组导入回到空闲。空数组清空点线,单点只生成起飞点。销毁后调用抛错。导入不调整视野,不提供文件解析功能。

更新航点扩展属性

updateWaypoint(pointOrUuid: Feature<Point> | string, properties: Partial<RouteWaypoint>): boolean 按 UUID 或当前航线的 Feature 浅合并 extData。可更新起飞点和普通航点,无需切换编辑模式;不改变模式、选择、坐标、连线或内部显示标记。

const waypoint = controller.exportWaypoints()[1]
if (waypoint?.uuid) {
  controller.updateWaypoint(waypoint.uuid, {
    pointSpeed: 15,
    height: 100,
    taskName: '巡检拍照',
    actions: { shoot: true }
  })
}
  • 成功返回 true 并同步通知 subscribe(),后续状态快照和导出包含更新值。未知 UUID、其他航线或已失效的 Feature、未完成插入的临时点、销毁后调用以及空属性对象返回 false,不通知。
  • properties 必须为非数组对象;禁止包含 uuid、indexNo、longitude、latitude,即使值为 undefined 也会抛错。修改位置使用地图编辑,整体替换使用 importWaypoints()。
  • 仅对 height、direction 复用有限数值校验,height 允许负数;校验失败抛错,不写入任何属性。gimbalPichAngle、shootType、supportLightType、pointSpeed、waylineId 及其他纯业务字段不做运行时校验,由调用方决定合法值。
  • 未提供的字段保留;嵌套对象或数组整体替换并保留引用,不做深合并。不会修改传入对象。heading、isTakeoff、isAircraft 等同名扩展字段只写入 extData,不会覆盖内部状态。
  • 导入、更新和导出保留负高度。adjustWaypointHeight() 仍将结果限制到最低 0 米;飞控动作帧(包括水平移动、转向和升降)也会将临时飞机高度限制到最低 0 米,不影响直接设置已记录航点的负高度。
  • 更新已记录航点不改变正在操控的临时飞机。直接 feature.set('extData', ...) 仍可使用,但不执行此方法的校验,也不自动通知控制器订阅。

批量更新全部航点

updateAllWaypoints(properties: Partial<RouteWaypoint>): boolean 将同一组扩展属性浅合并到全部已记录航点,包含起飞点,不包含临时飞机和尚未完成的插点。

controller.updateAllWaypoints({
  height: -20,
  pointSpeed: 15,
  shootType: 'custom-shoot',
  reachShoot: 1
})

校验、扩展字段透传与嵌套引用规则和 updateWaypoint() 相同;各航点分别合并自身已有属性,不改变 UUID、坐标、模式或选中航点。先校验全部合并结果,校验失败时整批不写入、不通知;成功写入全部目标返回 true,通过 subscribe() 通知一次最终状态。空航线、空属性对象或销毁后调用返回 false,不通知。

底层 Feature 的属性事件仍逐点触发。如果尚有目标待写入,而业务在这些事件中同步清空、重新导入、销毁或移除待更新航点,下一次写入前会停止旧批次并返回 false;已经写入的属性不回滚。业务面板优先使用控制器的 subscribe(),避免在底层属性监听中修改正在批量更新的航线。

导出航点

exportWaypoints(): RouteWaypoint[] 返回当前航线的数据副本,可直接用于保存或再次调用 importWaypoints()。

controller.importWaypoints(points)
// 绘制或编辑完成后保存
const data = controller.exportWaypoints()
const json = JSON.stringify(data)
  • 导出包含每个航点的稳定 uuid,导出再导入保留标识;缺省标识的旧数据仍可导入。
  • 数组包含起飞点及已记录航点,顺序与连线一致;不包含线要素、中点手柄或未打点的实时飞机。
  • indexNo 按当前顺序从 0 连续生成,支持插入和删除后再次导入;不向 Feature 写入序号。
  • longitude、latitude 取 Point 的当前坐标;拖动未结束时读取的是当前临时位置,持久化应在编辑完成后进行。
  • 保留所有扩展业务属性,以及 height、waylineId、pointSpeed、gimbalPichAngle、direction、shootType、supportLightType。已声明字段未设置时,数值填 0、字符串填空字符串;扩展字段不补默认值;direction 优先取 extData.direction,缺省取绘制时记录的 heading,不按线段重新计算。
  • 每次返回新数组和新的顶层对象,修改顶层字段不影响航线;嵌套对象和数组共享引用。不导出 Feature 自身的内部属性,extData 业务属性原样保留。空航线、清空和销毁后返回 [],读取不会停止操控或切换编辑模式。

编辑与飞行配置

RouteConfig.edit 和 setEditConfig(options) 接受 mode: 'midpoint' | 'snap' 与 pixelTolerance。默认 midpoint、容差 10 像素;容差必须为有限正数,配置增量合并。配置失败保留当前选择和拖动;有效更新在编辑模式下取消未完成拖动、清除旧选择并重建手柄。

route.setMode('edit')
route.setEditConfig({ mode: 'snap', pixelTolerance: 15 })
route.removeWaypoint() // 删除选中普通航点;失败返回 false
route.adjustWaypointHeight(1) // 选中航点升高 1 米,最低 0
route.setMode('idle') // 完成编辑并保留结果

中点模式拖动固定手柄插入航点,吸附模式沿整条线定位临时点后拖动插入;悬停和单击不插点。拖动与插入会同步线坐标,重复坐标回滚。selectWaypoint(uuid) 可主动选中普通航点,getSelectedWaypoint() 读取鼠标或程序选中的底层 Feature;removeWaypoint(pointOrUuid?, options?) 支持按 UUID 或 Feature 删除,省略目标时操作当前选择;adjustWaypointHeight(delta, point?) 省略航点参数时调整当前选择。起飞点不能删除,但可以显式传入起飞点调整高度。删除全部普通点后保留起飞点并移除线。

中点模式移动已有航点时复用手柄,只更新相邻两段中点;增删航点等结构变化才重建。批量属性更新复用数据源成员索引检查剩余目标,避免逐点扫描整条航线;仍保留同步回调导致目标失效时中断旧批次的保护。

创建配置的 speed、turnSpeed、verticalSpeed 默认分别为 10 m/s、60°/s、1 m/s,均为有限正数。新启动飞控从最后确认的航点及其高度开始,机头朝北;重复进入 control 保持当前飞机状态。按球面距离计算位移,移动时视野跟随,手动平移不修改飞机位置;单独转向、升降和打点不改变地图中心。动画帧停顿最多计入 100ms。编辑单次高度步长不受飞控速度影响。临时飞机不进入导出结果,持久化拖动结果应等待拖动结束。

动态更新飞行速度

setFlightConfig(options: RouteFlightConfig): void 可在任意未销毁状态增量更新飞行参数,包括尚无起飞点、正在操控和编辑模式。

route.setFlightConfig({ speed: 15 }) // 水平速度 15 米/秒,其余参数保留
route.setFlightConfig({
  speed: 20,
  turnSpeed: 90, // 转向速度,度/秒
  verticalSpeed: 2 // 升降速度,米/秒
})
console.log(route.getState().aircraft?.speed) // 已有航点时为 20
  • 省略或传入 undefined 的字段保留当前值;不会保留传入对象的引用。原有创建参数仍位于 RouteConfig 顶层。
  • 参数必须为非数组对象,速度必须为有限正数;0、负数、NaN、无穷值和字符串均抛错。整批校验成功后才写入,失败保留原配置和飞行状态,不通知。
  • 正在操控时,后续动画帧使用新参数;不重启会话、不移动飞机、不重置航向、高度、按键或面板动作,也不取消编辑选择。停止运动仍使用输入释放或 setRouteControls([])。
  • 更新后的配置在模式切换、清空、导入航点和重新进入操控后保留;销毁后调用抛错。不会修改已记录航点的 pointSpeed 或导出数据;updateWaypoint() 和 updateAllWaypoints() 也不会改变飞行参数。
  • getState() / subscribe() 的 aircraft.speed 和供自定义飞机样式读取的 Feature speed 同步更新;一次有效配置变化只通知一次,空配置或等值更新不通知。

十一、类型定义(core/types.ts,export * from './core/types')

interface CustomMap extends OlMap {
  activeBaseLayer?: string
  changeBaseLayer?: () => void
  getLayerByName?: (name: string) => Layer<Source> | undefined
  removeLayerByName?: (name: string) => void
  changeLayersByNames?: (activeNames: string[], allLayerNames: string[]) => void
}

type MassData = {
  _uuid?: string
  id: string
  style: number
  name: string
  status: string
  width?: number
  height?: number
  crsForm?: CRSTypes
  rawData: Record<string, any>
  lnglat: [string, string] | [number, number]
}

enum GeometryType {
  LineString = 'LineString',
  MultiLineString = 'MultiLineString',
  Polygon = 'Polygon',
  MultiPolygon = 'MultiPolygon',
  Invalid = 'Invalid'
}

完整导出还包括:VectorConfig、ClusterConfig、IconConfig、WmtsSourceOptions、GetImgUrl、GetImgUrlFn、HzsyType、ZheJiangType、WorldLayerType、WorldLayerLocalOptions、GeometryCoordinates、RouteConfig、RouteEditConfig、RouteFlightConfig、RouteWaypoint、RouteControl。


图层约定属性

工具库通过图层 properties 标记元信息,维护时请保持一致:_isBaseLayer、_layerName、_isCustom、_isVectorLayer、_isClusterLayer、_isMassMarksLayer、_selectInteraction、_resolutionChangeKey、_uuidSet、_dataList、_vectorConfig、_isAutoFit、_massMarksStyles、_statusNum。

构建

构建保留 ESM 与 CommonJS 桥接入口;checks.legacyCjs: false 仅关闭 tsdown 的 CommonJS 格式迁移建议,其他构建警告仍正常输出。

pnpm --dir packages/map-utils build       # tsdown 产出 esm + cjs + d.ts(压缩)
pnpm --dir packages/map-utils check:types # tsc --noEmit
pnpm --dir packages/map-utils test        # build + check:types + node:test
pnpm --dir packages/map-utils dev         # watch 模式

新增回归位于 test/data-regressions.test.ts、test/lifecycle-and-worker.test.ts;示例包 map-utils-layers.spec.ts 在 Chromium 中验证嵌套图层移除、双地图隔离及千点连续缩放只跨档更新样式。

注意事项

  • 地图归属:省略可选地图参数时先创建单例;多地图调用显式传入所属地图。中心按地图保存,销毁独立地图不清空单例。emitter 仍是包级共享事件总线,业务订阅需按数据区分来源并自行解绑。
  • 坐标系:输入数据可通过 config.crsForm / data.crsForm 指定源坐标系,内部统一转为 WGS84。
  • 天地图令牌:createMapWorldLayer 在线模式与 createZheJiangLayer 内置了默认 tk,正式项目请传入自有令牌;createMapWorldLayer 第三个 config 参数的本地 { url } 模式无需令牌。

版本与发布记录

map-ol-utils 共发布 129 个版本,首次发布于 2025/04/10;过去 12 个月发布了 30 个版本。

版本发布日期距今
1.2.10latest2026/11/04今天
1.2.92026/11/041 天前
1.2.82026/11/041 天前
1.2.72026/11/031 天前
1.2.62026/11/031 天前
1.2.52026/11/032 天前

关于 map-ol-utils 的常见问题

map-ol-utils 是什么?

OpenLayers实用程序与浏览器端天地图私有VTS的加载和转换

如何安装 map-ol-utils?

在项目目录运行 npm install map-ol-utils,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:ol(^10.10.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

map-ol-utils 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

map-ol-utils 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

map-ol-utils 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.18.0 || ^24.0.0 || >=26.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

map-ol-utils 可以商用吗?

map-ol-utils 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

map-ol-utils 还在维护吗?

最新版本 1.2.10 发布于 2026/11/04(今天);至今共发布 129 个版本,其中过去 12 个月发布了 30 个;首次发布于 2025/04/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。