Skip to content

gis-cesium 库开发规范

适用于 packages/gis-cesium — 项目核心 SDK。 这是整个技术栈中复杂度最高、约束最严的包。

定位

@nexa/gis-cesium 是 CesiumJS 的扩展 SDK。它保留 Cesium 公共 API 的可组合性,并对跨产品复用、资源生命周期、性能策略和版本兼容等复杂能力进行工程化封装。

  • GIS 引擎CesiumViewer 生命周期管理、Entity CRUD、时钟控制
  • 特效系统:动态可视化效果(探测/爆炸/轨迹等)
  • 插件体系viewer.extend(mixin) 模式的能力扩展
  • 材质库:自定义 GLSL 材质(闪烁/脉冲/流动线等)
  • 图层管理:GeoJSON/底图等图层 CRUD
  • 工具集:坐标转换、卫星轨道计算、时间工具等

扩展边界

  • 允许公共 API 使用 Cesium 的稳定公共类型,不为 ViewerEntityCartesian3JulianDate 创建无语义包装
  • 必须封装事件监听、DOM、Entity、Primitive、PostProcessStage、ParticleSystem 等资源的所有权和释放流程
  • 必须隔离 Cesium 私有 API;私有 API 只能进入专用兼容适配器,禁止散落在 Effect、Plugin 和产品代码中
  • 禁止仅为“业务不能调用 Cesium”而重复包装一对一原生方法
  • CesiumViewer 提供托管式启动体验;接受 Viewer 的扩展能力应兼容原生 Cesium.Viewer

资源所有权

创建资源的模块就是资源 owner。owner 必须保存资源引用,并提供幂等释放行为:

资源最低释放要求
Entity / Primitive / DataSource只移除自身创建的实例
ScreenSpaceEventHandler调用 destroy(),不得遗留输入监听
clock / scene 事件保存 remove callback 并调用
DOM 节点从挂载容器移除并清理引用
PostProcessStage / ParticleSystem从所属 collection 移除,必要时销毁

调用方传入的 Viewer、集合、Entity 和外部资源默认归调用方所有,SDK 禁止越权销毁

目录结构

packages/gis-cesium/
├── src/lib/                          # 【库代码】— 对外发布
│   ├── index.ts                      # 总入口:export * from './NexaCesium'
│   ├── NexaCesium.ts             # 版本号 VERSION + re-export 所有模块
│   └── core/
│       ├── AirViewer.ts              # CesiumViewer 主类
│       ├── types.ts                  # CesiumViewerOptions / ClockOptions
│       ├── BaseMapManager.ts         # 底图管理器
│       ├── TerrainManager.ts         # 地形管理器
│       ├── effects/                  # 动态特效
│       │   ├── index.ts / types.ts
│       │   └── XxxEffect.ts
│       ├── plugins/                  # Viewer 扩展
│       │   ├── index.ts / types.ts
│       │   └── viewerXxxMixin.ts
│       ├── materials/                # 自定义材质
│       │   ├── index.ts / types.ts / Material.ts
│       │   └── Shaders/
│       ├── entities/                 # 实体系统
│       ├── entities-connection/      # 实体连线特效
│       ├── layers/                   # 图层管理
│       ├── tools/                    # 工具函数
│       ├── utils/                    # 底层工具
│       ├── mouse-events/             # 鼠标交互
│       ├── draw/                     # 绘制工具
│       └── popup/                    # 弹窗
├── src/examples/                     # 示例中心(auto-discover)
├── src/components/ / views/ / routers/  # 示例 App UI
├── src/composables/                  # 示例 App composable
├── src/assets/                       # 静态资源(3D模型/地形/底图)
├── scripts/                          # 辅助脚本
└── docs/                             # VitePress 文档站

模块开发流水线

每个新功能模块必须按以下步骤执行:

Phase 1: 模块识别

根据功能特征确定模块类型(详见 AGENTS.md 模块模式约束表):

特征→ 模块类型→ 目录
有 on/off/destroy 生命周期effecteffects/
通过 viewer.extend() 注入pluginplugins/
涉及 Cesium MaterialProperty/GLSLmaterialmaterials/
Entity 创建工厂entityentities/
独立纯函数,不依赖 viewertooltools/
CRUD 管理图层layerlayers/
底层渲染工具(FBO 等)utilutils/

Phase 2: 类型定义先行

1. 在目标模块的 types.ts 中定义 Options 接口
2. 所有公共 interface 必须有 JSDoc
3. 可选参数提供默认值说明

Phase 3: 实现

按模块类型模板编写实现文件(详见 .claude/skills/create-feature.md)。

共同要求:

  • 所有导出函数/类必须有完整 JSDoc(含 @example
  • 类使用 PascalCase,函数使用 camelCase
  • 私有成员以 _ 前缀
  • Cesium 类型通过 import type * as Cesium from 'cesium' 导入

Phase 4: 注册导出

1. 模块 index.ts 添加导出
2. NexaCesium.ts 确认 re-export(通常模块 index 已覆盖)

Phase 5: 示例(可视化功能必须)

src/examples/templates/<Name>/ 创建:

  • index.ts — 示例定义(key 必须全局唯一)
  • index.vue — Vue 容器
  • index.code.ts — 可运行代码(使用全局 NexaGIS / Cesium)

Phase 6: 验证

bash
pnpm lint
pnpm typecheck
pnpm --filter @nexa/gis-cesium run build:lib
pnpm --filter @nexa/gis-cesium test

Effect 开发规范

类结构

typescript
import type { XxxOptions } from './types'
import type * as Cesium from 'cesium'

export class XxxEffect {
  private _enabled = false
  private _source: Cesium.Entity
  // ...

  constructor(options: XxxOptions) {
    this._source = options.source
    // 参数校验(必填参数在构造函数中检查)
  }

  /** 启用效果,将效果实体添加到 collection */
  on(collection: Cesium.EntityCollection): void {
    if (this._enabled) return  // 幂等
    this._enabled = true
    // 添加效果实体
  }

  /** 禁用效果,移除效果实体 */
  off(collection: Cesium.EntityCollection): void {
    if (!this._enabled) return
    this._enabled = false
    // 移除效果实体
  }

  /** 销毁效果,释放所有引用 */
  destroy(): void {
    this._enabled = false
    // 清理引用、移除事件监听
  }
}

export default XxxEffect

要求

  • on/off 必须幂等(重复调用不产生副作用)
  • off 只释放运行期资源,允许后续再次 on
  • destroy 释放全部内部资源;是否允许再次启用必须由公共契约明确说明
  • 已发布 Effect 暂不统一修改方法签名;签名迁移必须使用 @deprecated 过渡,禁止批量破坏兼容性
  • 构造函数中校验必填参数

Plugin 开发规范

函数签名

typescript
export function viewerXxxMixin(
  viewer: Cesium.Viewer,
  options?: XxxMixinOptions
): void {
  // 通过 Object.defineProperties 挂载 API 到 viewer
  Object.defineProperties(viewer, {
    xxx: {
      get: () => ({ /* 暴露的 API */ }),
      configurable: true,
    },
  })
}

要求

  • 函数名以 viewer 开头,以 Mixin 结尾
  • API 通过 viewer.xxx 访问
  • 选项接口定义在 plugins/types.ts

Material 开发规范

类结构

typescript
import { CsmMaterial } from './Material'
import shaderSource from './Shaders/Xxx.glsl'
import type { XxxMaterialValue } from './types'

export class XxxMaterialProperty {
  constructor(options: XxxMaterialPropertyOptions) {
    // 注册材质到 Cesium
    CsmMaterial.addMaterial('XxxType', {
      fabric: {
        type: 'XxxType',
        uniforms: { /* ... */ },
        source: shaderSource,
      },
    })
  }

  getType(): string { return 'XxxType' }
  getValue(time: Cesium.JulianDate, result?: XxxMaterialValue): XxxMaterialValue { ... }
  equals(other: Cesium.Property): boolean { ... }
}

要求

  • GLSL 着色器文件放在 Shaders/ 子目录
  • 通过 CsmMaterial.addMaterial() 注册
  • 实现 getType/getValue/equals

公共 API 管理

NexaCesium.ts 规则

  • 这是库的唯一入口 barrel 文件
  • 每个核心模块至少在此有一个 re-export
  • 版本号 VERSION 在此定义
  • 修改此文件视为破坏性变更(需 PR 审查)

导出规范

typescript
// 默认导出 + 命名导出(Effect 类)
export { default as XxxEffect } from './core/effects/XxxEffect'

// 纯命名导出(Plugin 函数、Tool 函数)
export { viewerXxxMixin } from './core/plugins/viewerXxxMixin'

// 纯命名导出(Material 类)
export { XxxMaterialProperty } from './core/materials/XxxMaterialProperty'

禁止事项

  • ❌ 在库代码中引入 src/App.vuesrc/main.tssrc/views/ 等示例 App 文件
  • ❌ 在库代码中引入 naive-ui 等 UI 组件(库代码应保持 UI 无关)
  • ❌ 修改 NexaCesium.ts 的导出而不更新文档和示例
  • ❌ 在公共 API 中使用 any(packages/ 下 no-explicit-any: error
  • ❌ 删除或修改现有公共 API 的签名(视为 Breaking Change)