@loongbao-web-gis-utils/draw-utils-grid-openlayer
v1.0.8
Published
Web GIS 通用绘图工具 OpenLayer 适配层 — WebGLVectorLayer 高性能渲染
Maintainers
Readme
使用手册
快速开始
1. 安装依赖
npm install @loongbao-web-gis-utils/draw-utils-grid-core
npm install @loongbao-web-gis-utils/draw-utils-grid-openlayer
npm install ol2. 创建地图
import OlMap from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import XYZ from 'ol/source/XYZ.js';
const map = new OlMap({
target: 'map',
layers: [
new TileLayer({
source: new XYZ({ url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' }),
}),
],
view: new View({ center: [116.404, 39.915], zoom: 12 }),
});3. 初始化绘制工具
import { DrawTool } from '@loongbao-web-gis-utils/draw-utils-grid-core';
import { OpenLayerAdapter } from '@loongbao-web-gis-utils/draw-utils-grid-openlayer';
const adapter = new OpenLayerAdapter(map);
const tool = new DrawTool(adapter, {
insertBiz: (feature) => {
// 业务侧保存数据,返回 true 表示成功
return saveToDatabase(feature);
},
delBiz: (feature) => {
// 业务侧删除数据,返回 true 表示成功
return deleteFromDatabase(feature.id);
},
});4. 开始绘制
tool.startDraw({
type: 'h3', // 网格类型: 'h3' | 'geohash'
accuracy: 6, // 精度
drawNeighbor: false, // 是否绘制相邻网格
lineWidth: 2, // 线条宽度
primaryColor: '#0000FF', // 颜色
lineOpacity: 1, // 线条透明度
fillOpacity: 0.3, // 填充透明度
enableDel: true, // 允许点击删除
showLen: true, // 显示边长
showArea: true, // 显示面积
debug: false, // 调试模式
});5. 绑定地图事件
import { toLonLat } from 'ol/proj.js';
map.on('dblclick', (evt) => {
const coord = map.getCoordinateFromPixel(evt.pixel);
const lonLat = toLonLat(coord, map.getView().getProjection());
tool.onDblClick(lonLat); // 双击绘制
});
map.on('singleclick', (evt) => {
const coord = map.getCoordinateFromPixel(evt.pixel);
const lonLat = toLonLat(coord, map.getView().getProjection());
tool.onClick(lonLat); // 单击选择/删除
});6. 停止绘制
tool.stopDraw();交互流程
绘制流程
业务方调用 startDraw() → 进入绘制模式(仅允许绘制一次)
↓
用户双击地图 → 触发 onDblClick(坐标)
↓
工具生成要素 → 调用 insertBiz 回调
↓ (返回 true)
要素添加到 WebGL 图层 → 生成边长/面积标签(如启用)
↓
绘制模式自动结束,如需连续绘制需重新调用 startDraw()删除流程
startDraw({ enableDel: true }) → 启用删除
↓
用户单击要素 → 触发 onClick(坐标)
↓
工具检测命中要素 → 显示删除图标
↓
用户单击删除图标 → 弹出 Vue3 确认框
↓
确认: delBiz 回调 → 返回 true 则删除要素+标签
返回 false 则还原要素
取消: 不做任何操作API 设计
DrawTool
核心绘制工具类。
class DrawTool {
readonly drawEnabled: boolean;
constructor(adapter: IMapAdapter, callbacks?: DrawCallbacks);
startDraw(options?: DrawOptions): void;
stopDraw(): void;
onDblClick(coordinate: [number, number]): void;
onClick(coordinate: [number, number]): void;
getFeatures(): FeatureInfo[];
getOptions(): Readonly<Required<Omit<DrawOptions, 'oldFeatures'>>>;
}| 方法 | 说明 |
|------|------|
| startDraw(options) | 开始绘制模式,创建图层并配置参数。每次调用仅允许一次绘制,绘后自动结束 |
| stopDraw() | 停止绘制,销毁图层及所有要素、标签、弹窗 |
| onDblClick([lng, lat]) | 处理双击事件,在点击位置生成网格要素 |
| onClick([lng, lat]) | 处理单击事件,检测要素命中并触发删除交互 |
| getFeatures() | 返回当前图层中所有要素的副本 |
| drawEnabled | 只读,当前是否处于可绘制状态 |
DrawOptions
interface DrawOptions {
type?: GridType; // 网格类型,默认 'h3'
accuracy?: number; // 精度,H3:1-15, GeoHash:1-12,默认 6
drawNeighbor?: boolean; // 是否绘制相邻网格,默认 false
lineWidth?: number; // 线条宽度(px),默认 2
primaryColor?: string; // 主题色(CSS颜色),默认 '#0000FF'
lineOpacity?: number; // 线条透明度 0-1,默认 1
fillOpacity?: number; // 填充透明度 0-1,默认 0.3
enableDel?: boolean; // 是否允许删除,默认 false
showLen?: boolean; // 是否展示边长,默认 false
showArea?: boolean; // 是否展示面积,默认 false
debug?: boolean; // 调试模式,控制台打印日志,默认 false
oldFeatures?: FeatureInfo[]; // 已有要素列表
}DrawCallbacks
interface DrawCallbacks {
insertBiz?: (info: FeatureInfo) => boolean;
delBiz?: (info: FeatureInfo) => boolean;
}| 回调 | 说明 |
|------|------|
| insertBiz(feature) | 新增要素时调用。返回 true 要素生效,false 取消绘制 |
| delBiz(feature) | 删除要素时调用。返回 true 删除生效,false 还原要素 |
FeatureInfo
interface FeatureInfo {
id: number;
type: GridType;
grids: GridInfo[];
drawNeighbor: boolean;
accuracy: number;
primaryColor: string;
lineWidth: number;
lineOpacity: number;
fillOpacity: number;
}
interface GridInfo {
primary: boolean;
gridKey: string;
wkt: string;
locationType: LocationType;
}枚举
enum GridType {
H3 = 'h3',
GEOHASH = 'geohash',
}
enum LocationType {
CENTER = 'CENTER',
NORTH = 'NORTH',
SOUTH = 'SOUTH',
EAST = 'EAST',
WEST = 'WEST',
NORTHEAST = 'NORTHEAST',
NORTHWEST = 'NORTHWEST',
SOUTHEAST = 'SOUTHEAST',
SOUTHWEST = 'SOUTHWEST',
}IMapAdapter(适配器接口)
interface ILayerHandle {
readonly id: symbol;
}
interface IMapAdapter {
readonly mapInstance: unknown;
createLayer(): ILayerHandle;
addFeature(layer: ILayerHandle, feature: FeatureInfo): void;
removeFeature(layer: ILayerHandle, featureId: number): void;
clearLayer(layer: ILayerHandle): void;
destroyLayer(layer: ILayerHandle): void;
findFeatureIdAtCoordinate(layer: ILayerHandle, coordinate: [number, number]): number | null;
showDeleteWithConfirm(layer: ILayerHandle, feature: FeatureInfo, coordinate: [number, number]): Promise<boolean>;
hideDeleteIcon(layer: ILayerHandle): void;
showFeatureLabel(layer: ILayerHandle, featureId: number, text: string, coordinate: [number, number], labelKind?: string, color?: string): void;
hideFeatureLabel(layer: ILayerHandle, featureId: number, labelKind?: string): void;
}GridStrategy(网格策略接口)
interface GridStrategy {
readonly type: GridType;
latLngToGridKey(lng: number, lat: number, accuracy: number): string;
getNeighbors(gridKey: string, accuracy: number): NeighborInfo[];
gridKeyToWKT(gridKey: string): string;
calculateEdgeLength(gridKey: string, accuracy: number): number;
calculateArea(gridKey: string, accuracy: number): number;
formatLength(meters: number): string;
formatArea(squareMeters: number): string;
buildFeature(id, lng, lat, accuracy, drawNeighbor, primaryColor, lineWidth, lineOpacity, fillOpacity): FeatureInfo;
}通过工厂函数获取策略实例:
import { getGridStrategy, GridType } from '@loongbao-web-gis-utils/draw-utils-grid-core';
const h3Strategy = getGridStrategy(GridType.H3);
const key = h3Strategy.latLngToGridKey(116.404, 39.915, 6);OpenLayerAdapter
import { OpenLayerAdapter } from '@loongbao-web-gis-utils/draw-utils-grid-openlayer';
const adapter = new OpenLayerAdapter(map); // map: ol/Map 实例基于 WebGLVectorLayer 的高性能渲染适配器,实现 IMapAdapter 的全部方法。
整体架构设计
分层架构
┌─────────────────────────────────────────┐
│ 业务集成方 │
│ (传入 Map 实例、事件、回调) │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ DrawTool (core) │
│ 核心绘制逻辑、要素管理、去重、ID生成 │
└──────┬────────────────────┬─────────────┘
│ │
┌──────▼──────┐ ┌────────▼──────────────┐
│ GridStrategy │ │ IMapAdapter │
│ (策略模式) │ │ (适配器接口) │
│ H3 / GeoHash│ │ createLayer() │
│ 网格计算 │ │ addFeature() │
│ WKT 生成 │ │ removeFeature() │
│ 边/面积计算 │ │ showDeleteWithConfirm│
└──────────────┘ │ ... │
└────────┬─────────────┘
│
┌────────▼─────────────┐
│ OpenLayerAdapter │
│ (adapter-openlayer) │
│ WebGLVectorLayer │
│ OL Overlay / Style │
└───────────────────────┘核心模块职责
| 模块 | 位置 | 职责 |
|------|------|------|
| DrawTool | packages/core | 绘制生命周期管理、要素增删、事件处理 |
| GridStrategy | packages/core | H3/GeoHash 网格的坐标转换、邻居计算、WKT生成 |
| IMapAdapter | packages/core | 定义 GIS 框架适配器的接口契约 |
| OpenLayerAdapter | packages/adapter-openlayer | 实现 IMapAdapter,对接 OpenLayer WebGLVectorLayer |
设计模式
Adapter 模式 — 核心层定义 IMapAdapter 接口,各 GIS 框架通过实现该接口完成适配。当前支持 OpenLayer,后续可扩展 MapLibre、Cesium 等。
Strategy 模式 — GridStrategy 抽象 H3 和 GeoHash 的网格计算差异,通过 GridStrategyFactory 按类型获取对应策略实例。
坐标系统透明化
核心工具层(DrawTool / GridStrategy)对坐标系统完全透明:
- 所有坐标以
[number, number]透传,不做任何投影变换 - 投影相关处理由适配器层(OpenLayerAdapter)在读写数据时根据地图实例动态完成
包命名规范
@loongbao-web-gis-utils/draw-utils-grid-core ← 核心层
@loongbao-web-gis-utils/draw-utils-grid-openlayer ← OpenLayer 适配器
@loongbao-web-gis-utils/draw-utils-grid-{框架名} ← 后续框架适配器注意事项
- 多实例: DrawTool 支持并行实例化,每个实例独立管理图层
- 去重: 同一类型、同一精度的网格不允许完全重叠绘制
- ID 生成: 工具自动生成全局唯一的 Number 类型 ID
- oldFeatures: 支持传入已有要素,其 ID 不会被修改
- 删除确认: 删除确认框由工具负责维护,基于 Vue 3 实现
- 单次绘制: 每次 startDraw() 只允许一次绘制,绘后自动结束,连续绘制需重复调用
