map-ol-utils
v1.0.0
Published
OpenLayers utilities with browser-side Tianditu private VTS loading and conversion
Maintainers
Readme
map-ol-utils
目录:
packages/map-utils| npm 包名:map-ol-utils
基于 OpenLayers 的地图工具库,封装了地图实例管理、底图创建、矢量/面/线/聚合/海量点图层、要素交互选择、视野自适应、WMTS 接入,以及天地图私有 VTS 的浏览器加载与标准 MVT 转换能力。
特性
- 单例地图实例:模块内维护一个全局
instance,多数工具函数直接操作该单例,无需层层传参。 - 多种底图:杭州规自瓦片、全国天地图(WMTS)、浙江省天地图(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 与使用约定安装
pnpm add map-ol-utils ol gcoordol 是对等依赖(要求 ^10.10.0),需由宿主项目安装。tdt-vts-core 是普通依赖,由包管理器自动安装。
快速开始
import { createMapInstance, createBusinessLayer, 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)
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() // 同时释放解码 Workerhosts 与 style 均为必填项。传入地址时,本包会请求并校验 Style v8 JSON,服务端需允许当前页面跨域访问;也可以直接传入包含 sources 和 layers 的样式对象。
业务调用只需要 createTdtVtsLayer();底层转换 API 保留用于兼容已有调用。
接口(API)详解
约定:独立工具函数默认操作
createMapInstance()创建的模块单例;createMap()返回独立地图,其挂载的图层方法只操作自身。
一、地图实例(map/create-map.ts)
createMapInstance(config?)
创建并缓存地图单例;若已存在则直接返回,不会重建。
function createMapInstance(config?: Record<string, any>): CustomMap| 参数 | 类型 | 默认 | 说明 |
| -------- | -------- | ---- | ----------------------------------------- |
| config | object | {} | 透传给 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 }| 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| config | object | {} | ol/View 配置;可额外传 layers 覆盖默认底图、baseLayer 标记当前底图名 |
const map = createMap({ zoom: 12, center: [121.43, 28.66] })getMapInstance()
function getMapInstance(): CustomMap返回当前单例(等价于导出的 instance 变量)。
disposeMap()
function disposeMap(): void销毁单例:移除全部覆盖物、清空交互、移除并释放(source.dispose() + layer.dispose())所有图层,setTarget('') 后将 instance 置为 undefined。未初始化或重复调用时会安全跳过。
resetMap(config?)
function resetMap(config?: { center?: Coordinate; isClearAll?: boolean }): void
// 默认:{ center: 上次中心, isClearAll: true }清除所有非底图图层(内部调用 removeAllLayers)并复位缩放与中心。
resetZoomAndCenter(center?)
function resetZoomAndCenter(center?: [number, number]): 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?)
全国天地图 WMTS 图层。
function createMapWorldLayer(layerName: WorldLayerType, tk?: string): Tile
// WorldLayerType = 'vec_c' | 'cva_c' | 'img_c' | 'cia_c' | 'ter_c' | 'cta_c' | 'ibo_c'| 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| layerName | WorldLayerType | — | 形如 img_c,内部按 _ 拆分为图层名与 matrixSet |
| tk | string | 内置默认令牌 | 天地图访问令牌,建议自行传入 |
map.addLayer(createMapWorldLayer('img_c', '你的天地图令牌'))createZheJiangLayer(layerName?, tk?)
浙江省天地图 XYZ 图层。
function createZheJiangLayer(layerName?: ZheJiangType, tk?: string): Tile
// ZheJiangType = 'emap' | 'emap_lab' | 'imgmap' | 'imgmap_lab'(默认 'emap')getLayerByName(name)
function getLayerByName(name: string): Layer<Source> | undefined按图层 _layerName 属性或 className 查找首个匹配图层。
removeLayerByName(name)
function removeLayerByName(name: string): 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)
核心矢量图层构造函数:将业务数据组装为 GeoJSON(自动推断线/面几何类型),生成默认/激活样式并写入要素属性,可选开启点击交互。
function createBusinessLayer(
dataList: MassData[],
config: VectorConfig
): VectorLayer| config 关键字段 | 类型 | 说明 |
| --- | --- | --- |
| key | string | 数据中存放坐标数组的字段名 |
| isLine | boolean | 是否线图层(影响是否生成填充样式) |
| layerName | string? | 图层名(className) |
| openClick | boolean? | 是否创建单击选择交互 |
| colorKey / colorMap | string? / Map<string,string>? | 按数据字段值映射颜色 |
| fillColor / strokeColor | string? | 填充 / 描边色 |
| fillOpacity / strokeWeight | number? | 填充透明度 / 线宽 |
| activeFillColor / activeStrokeColor / activeStrokeWeight | … | 激活态样式 |
| crsForm | CRSTypes? | 源坐标系(默认 WGS84) |
| isAutoFit | boolean? | 选中后是否自动居中 |
图层附带属性:_isCustom、_isVectorLayer、_isAutoFit、_vectorConfig、_layerName、_dataList、_uuidSet。
const layer = createBusinessLayer(dataList, {
key: 'coordinates',
isLine: false,
openClick: true,
layerName: 'area',
colorKey: 'level',
colorMap: new Map([
['1', '#f00'],
['2', '#0f0']
]),
fillOpacity: 0.3,
strokeWeight: 2
})
map.addLayer(layer)createVectorLineLayer(dataList?, config) / createVectorFaceLayer(dataList?, config)
createBusinessLayer 的便捷包装,分别固定 isLine: true / isLine: false。
function createVectorLineLayer(
dataList?: MassData[],
config: VectorConfig
): VectorLayer
function createVectorFaceLayer(
dataList?: MassData[],
config: VectorConfig
): VectorLayerappendFeaturesByLayer(targetLayer, dataList, crsForm?)
向已有矢量图层追加要素(同步更新其 _uuidSet),复用图层 _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?)
function createMassMarksLayer(
dataList: MassData[],
styleList?: StyleLike[]
): VectorLayer按 item.style 索引在 styleList 中取样式创建要素图层(zIndex: 2000)。图层附带 _isMassMarksLayer、_statusNum、_dataList、_uuidSet 等属性。
addMassMarksLayer(getImgUrlFn)
高阶函数,返回的方法会生成样式、创建海量点图层、按 hasLine / hasFace 附加伴生矢量图层,并自动加入地图。
function addMassMarksLayer(
getImgUrlFn: GetImgUrlFn
): (
dataList?: MassData[],
iconConfig: IconConfig,
openClick?: boolean
) => VectorLayer| iconConfig 字段 | 类型 | 说明 |
| --- | --- | --- |
| baseUrl | string | 图标根地址 |
| markerIcon | string | 图标名前缀 |
| markerSize | [number, number] | 图标尺寸 |
| statusNum | number? | 状态数(默认 5) |
| activeScale | number? | 激活放大倍数(默认 1.3) |
| hasLine / hasFace | boolean? | 是否附带线 / 面图层 |
| isDynamic | boolean? | 是否动态图层(选中派发 dynamic-marker) |
| isAutoFit | boolean? | 选中是否自动居中 |
| layerName | string? | 图层名 |
| offset | [number, number]? | 偏移 |
| marksStyles | Style[]? | 直接指定样式(跳过自动生成) |
| vectorConfig | VectorConfig? | 伴生线/面图层配置覆盖 |
const add = addMassMarksLayer((baseUrl) => (n, e) => `${baseUrl}/${n}${e}`)
const layer = add(dataList, {
baseUrl: '/icons',
markerIcon: 'alarm',
markerSize: [32, 32],
statusNum: 5,
hasLine: true
})addFeaturesByLayer(targetLayer, dataList, config)
向海量点图层增量添加要素(复用图层 _massMarksStyles)。
function addFeaturesByLayer(
targetLayer: VectorLayer,
dataList: MassData[],
config: { clear: boolean }
): voidconfig.clear 为 true 时先清空数据源再添加。
updateMarksLayersByStatus(checkedStatusList?)
function updateMarksLayersByStatus(checkedStatusList?: string[]): MassData[]遍历所有海量点图层:status 不在勾选列表中的要素设为隐藏样式,否则恢复默认样式。返回当前保留显示的数据数组。
五、聚合图层(layer/cluster-layer.ts)
createClusterLayer(dataList, config)
function createClusterLayer(
dataList: ClusterData[],
config: ClusterConfig
): VectorLayer
type ClusterData = {
_uuid: string
name: string
style: number
lnglat: number[]
rawData: Record<string, any>
}
type ClusterConfig = {
distance: number
minDistance: number
singleStyles: Style[]
layerName?: string
openClick?: boolean
computedRadius?: (size: number) => number
buildStyle?: (feature: FeatureLike) => Style
}未传 buildStyle 时使用内置样式:聚合点半径与色相(hsla)随聚合数量变化;size === 1 时使用 singleStyles。
const cluster = createClusterLayer(dataList, {
distance: 80,
minDistance: 20,
singleStyles: [singlePointStyle],
openClick: true
})
map.addLayer(cluster)六、交互与选中(interaction/select.ts)
createSelectByLayer(vectorLayer)
为指定图层创建 ol/interaction/Select(单击、multi: false、hitTolerance: 10),命中要素时切换激活样式并派发 selected-feature 事件,支持聚合图层单点展开。交互会写入图层属性 _selectInteraction。
function createSelectByLayer(vectorLayer: VectorLayer): SelectactiveFeatureByUid(uid)
function activeFeatureByUid(uid: string): void在带 _isVectorLayer 且其 _uuidSet 含该 uid 的图层中,程序化触发对应要素的选中(派发 select 事件并加入选中集合)。
resetActiveFeature() / resetActiveVector()
function resetActiveFeature(): void // 清空 overlay + 触发各矢量图层 deselect
function resetActiveVector(): void // 清空 overlay + 清空各图层选择交互的要素集合七、覆盖物与视野(overlay/overlay.ts、map/view-fit.ts)
createOverlay(config?)
function createOverlay(config?: import('ol/Overlay').Options): Overlay
// 默认:{ id: 'popup-container', positioning: 'top-center' }未显式传 element 时,按 config.id 取 document.getElementById(id) 作为容器。
removeOverlays(key?) / removeAllLayers()
function removeOverlays(key?: string): void // 移除带指定属性键的 overlay(默认按 'position')
function removeAllLayers(): void // 移除并释放所有非底图(无 _isBaseLayer)图层 + overlaycreateAnimationMarker(imgUrl, data)
function createAnimationMarker(imgUrl: string, data: MassData): void以 data.lnglat(经 data.crsForm → WGS84 转换)创建动画标注覆盖物,先移除已有动画标注。data 中读取 width / height / lnglat / crsForm。
setFitViewByCoords / setFitViewByGeom / setFitViewByLayer
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 自适应三者默认 padding: [50,50,50,50]、duration: 1000,可由 fitOptions 覆盖。
setFitViewByCoords([
[120.1, 30.2],
[120.3, 30.4]
])
setFitViewByLayer(layer, undefined, { duration: 500 })getLayersDataList()
function getLayersDataList(): 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 | 要素属性对象 | 要素被选中(createSelectByLayer 内触发) |
| dynamic-marker | 动画标注配置 | 选中动态图层(_isDynamic)要素 1s 后 |
十、扩展能力
以下 API 补充天地图组合、标记、绘制与 GeoJSON 等能力;createMapInstance、createOverlay、disposeMap 等既有 API 的签名、默认值和行为保持不变。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
})
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):按缩放级别切换img、smallImg、largeImg,通过库的生命周期方法移除时自动解绑缩放监听。changeUnselectStyle(featureCollection):清空选择集合并恢复未选中样式。createLayerSelectByNames(layerNames, selectedHandler?, unSelectHandler?):按多个图层名选择标记。createMoveMarkerLayer(dataItem, config):创建单标记图层并加入当前地图。
绘制、GeoJSON 与视野
createDrawInteraction(type, vectorLayer, openTip?):创建绘制交互。getDrawStyle(feature, drawStyle?):为线和面绘制追加长度/面积标签。createGeoJsonLayer(geoJson, style, config?):直接从 GeoJSON 创建矢量图层。flyToAnimate(center, zoom?):使用两段缩放动画飞到目标点。
相关类型包括 LayerConfig、DataItemConfig、ElasticDataItemConfig。
十一、类型定义(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、ClusterData、IconConfig、WmtsSourceOptions、GetImgUrl、GetImgUrlFn、HzsyType、ZheJiangType、WorldLayerType、GeometryCoordinates。
图层约定属性
工具库通过图层 properties 标记元信息,维护时请保持一致:_isBaseLayer、_layerName、_isCustom、_isVectorLayer、_isClusterLayer、_isMassMarksLayer、_selectInteraction、_resolutionChangeKey、_uuidSet、_dataList、_vectorConfig、_isAutoFit、_massMarksStyles、_statusNum。
构建
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 模式注意事项
- 单例副作用:绝大多数独立函数依赖模块级
instance,请先调用createMapInstance;createMap只创建独立地图,disposeMap后instance为undefined。 - 坐标系:输入数据可通过
config.crsForm/data.crsForm指定源坐标系,内部统一转为WGS84。 - 天地图令牌:
createMapWorldLayer/createZheJiangLayer内置了默认tk,正式项目请传入自有令牌。
