npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@loongbao-web-gis-utils/draw-utils-grid-openlayer

v1.0.8

Published

Web GIS 通用绘图工具 OpenLayer 适配层 — WebGLVectorLayer 高性能渲染

Readme

使用手册

快速开始

1. 安装依赖

npm install @loongbao-web-gis-utils/draw-utils-grid-core
npm install @loongbao-web-gis-utils/draw-utils-grid-openlayer
npm install ol

2. 创建地图

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-{框架名}     ← 后续框架适配器

注意事项

  1. 多实例: DrawTool 支持并行实例化,每个实例独立管理图层
  2. 去重: 同一类型、同一精度的网格不允许完全重叠绘制
  3. ID 生成: 工具自动生成全局唯一的 Number 类型 ID
  4. oldFeatures: 支持传入已有要素,其 ID 不会被修改
  5. 删除确认: 删除确认框由工具负责维护,基于 Vue 3 实现
  6. 单次绘制: 每次 startDraw() 只允许一次绘制,绘后自动结束,连续绘制需重复调用