# 第一层 · earthsdk3 使用入门

> 本层面向"如何使用 earthsdk3"。覆盖入口管理器 `ESObjectsManager`、视口 `ESViewer`、内部对象管理器 `SceneObjectsManager`，以及创建/操作场景对象的通用模式与代码示例。
> 第三层（单个对象的属性/方法/事件）见 `objects/<类名>.md`；第二层（对象分类选型）见 `layer2-classification.md`。

## 一、心智模型

EarthSDK3 是**引擎无关**的三维地球可视化 SDK。核心思想：

- **核心抽象层**（`earthsdk3` 包）：定义场景对象（`ESSceneObject` 体系）、对象管理器、视口、类型系统，不依赖任何渲染引擎。
- **引擎适配层**（`earthsdk3-cesium` / `-ol` / `-ue`）：基于 Cesium / OpenLayers / Unreal Engine 实现 `ESSceneObject` 的具体渲染。一套对象 JSON，三个引擎可切换。
- **入口**：`ESObjectsManager`（习惯简称 `objm`）是开发者接触的第一个对象，它管理所有场景对象与视口。
- **对象-视口自动关联**：`objm.createSceneObject*` / `sceneTree.createSceneObjectTreeItem*` 创建的对象自动纳入 `SceneObjectsManager`；`ViewersManager` 监听对象增删事件，**自动**把对象加到所有匹配 `devTags` 的视口（内部 `viewer.add`，再按"视口类型 + 对象类型"映射创建引擎层 `EngineObject` 进行渲染）。开发者**无需手动 `viewer.add`**，对象创建即自动渲染。

典型使用范式：

```ts
import { ESObjectsManager } from 'earthsdk3';
import 'earthsdk3-cesium'; // 注册 Cesium 引擎实现（副作用导入）

// 1. 创建管理器
const objm = new ESObjectsManager();

// 2. 创建视口（绑定到页面容器）
const viewer = objm.createCesiumViewer(containerDiv);

// 3. 创建场景对象（推荐从 JSON 创建，便于序列化/持久化）
//    对象创建后自动纳入管理并自动渲染到所有视口，无需 viewer.add
const model = objm.createSceneObjectFromJson({
  type: 'ESGltfModel',
  name: '大楼',
  position: [116.39, 39.9, 0],
  url: '${earthsdk3-assets-script-dir}/assets/glb/building.glb',
});

// 4. 交互：监听点击、飞行相机、编辑对象……
viewer.clickEvent.don((e) => { /* ... */ });
viewer.flyIn([116.39, 39.9, 500], [0, -90, 0], 2);
```

> 约定：`objm` = `ESObjectsManager` 实例；`viewer` = `objm.activeViewer`。

---

## 二、ESObjectsManager（入口管理器）

`ESObjectsManager extends Destroyable`，聚合了场景对象管理、视口管理、场景树、相机视角集合、播放器、路径动画等子系统。

> 完整属性/方法/事件见 `managers/ESObjectsManager.md`。

### 2.1 创建 / 销毁

| 方法 | 说明 |
| --- | --- |
| `new ESObjectsManager(...args)` | 构造。无必填参数。 |
| `objm.destroy()` | 销毁管理器（继承自 Destroyable，释放所有子资源）。 |

### 2.2 场景对象 CRUD

| 方法 | 说明 |
| --- | --- |
| `createSceneObject(type: string \| ctor, id?: string)` | 按类型名或构造函数创建对象并纳入管理。 |
| `createSceneObjectFromClass(ctor, id?)` | 按构造函数创建（类型安全）。 |
| `createSceneObjectFromJson(json: {type, ...})` | **推荐**。从 JSON 创建，自动按 `type` 解析。 |
| `createSceneObjectFromUrl(url, id?)` | 从 URL 加载对象 JSON 创建。 |
| `destroySceneObject(sceneObject)` | 从管理器移除并销毁对象。 |
| `destroyAllSceneObjects()` | 销毁所有对象（跳过内置播放器、视角集合）。 |
| `getSceneObject(option?)` | 按 id 或 type 查询；无参返回全部对象。 |
| `getSceneObjectById(id)` | 按 id 查询。 |
| `get $refs` | 取带 `ref` 属性的对象映射（`objm.$refs.xxx`）。 |
| `static getSceneObjById(id)` | 静态版按 id 查询。 |

```ts
// 从 JSON 创建（最常用，便于持久化）
const poi = objm.createSceneObjectFromJson({
  type: 'ESPoi2D',
  position: [116.39, 39.9, 0],
  text: '北京',
});
// 按类型名创建
const line = objm.createSceneObject('ESGeoLineString');
line.points = [[116.39, 39.9, 0], [116.40, 39.9, 0]];
// 销毁
objm.destroySceneObject(poi);
```

### 2.3 视口 CRUD 与引擎切换

| 方法 | 说明 |
| --- | --- |
| `createCesiumViewer(container \| option)` | 创建 Cesium 视口。 |
| `createOpenLayersViewer(container \| option)` | 创建 OpenLayers 二维视口。 |
| `createUeViewer(container, uri, app, token?, ...)` | 创建 UE 视口（支持 uri/ws/project 三种连接方式）。 |
| `createViewer(option: ESVOption)` | 通用创建（`option.type` 决定引擎）。 |
| `switchToCesiumViewer(...)` / `switchToUEViewer(...)` | 切换引擎视口（同步视角与属性，默认销毁旧视口）。 |
| `switchViewer(option, viewSync?, attributeSync?, destroy?)` | 通用切换。 |
| `destroyViewer(viewer)` | 销毁指定视口。 |
| `get activeViewer` / `set activeViewer` | 当前活动视口（对象只在其活动视口中渲染编辑）。 |
| `get activeViewerChanged` | 活动视口变更事件。 |
| `get viewers` / `getViewers()` | 全部视口。 |
| `get viewerCreatedEvent` | 视口创建事件。 |
| `syncOtherViewersToActived` | 是否把其它视口同步到活动视口。 |

```ts
// 创建 Cesium 视口
const czmViewer = objm.createCesiumViewer(containerDiv);
// 切换到 UE 视口（视角自动同步）
objm.switchToUEViewer(containerDiv, 'http://localhost:9007/', 'earthsdk3');
// 切回 Cesium
objm.switchToCesiumViewer(containerDiv);
```

> `createUeViewer` 三种连接方式：① `uri + app + token`（像素流）；② `ws + esmsg`（WebSocket）；③ `project + baseUrl`（H5 云渲染）。

### 2.4 内置子系统

| 属性 | 类型 | 说明 |
| --- | --- | --- |
| `sceneObjectsManager` | `SceneObjectsManager` | 内部对象集合（见第三节）。 |
| `sceneTree` | `SceneTree` | 默认场景树（'default'）。加入场景树的对象自动随视口渲染。 |
| `getSceneTree(id?)` / `createSceneTree(id, itemDivHeight?)` | - | 场景树查询/创建。 |
| `cameraViewsManager` | `ESCameraViewCollection` | 内置相机视角集合（飞行/轮播）。 |
| `player` | `ESPlayer` | 内置播放器（驱动动画/路径）。 |
| `pathAnimationManager` | `PathAnimationManager` | 路径动画管理器（通道 + 播放器 + ESPath）。 |
| `sceneObjectEditingManager` | `SceneObjectEditingManager` | 编辑管理器。 |
| `dragstartDataMananger` | `DragStartDataManager` | 拖拽数据管理。 |

### 2.5 序列化（持久化）

| 属性 | 说明 |
| --- | --- |
| `get json` / `set json` | 项目 JSON（不含默认值）。`set` 时按 asset/viewers/sceneTree/viewCollection 装配。 |
| `get completeJson` | 完整 JSON（含默认值）。 |

```ts
// 保存
const project = objm.json;
localStorage.setItem('project', JSON.stringify(project));
// 恢复
objm.json = JSON.parse(localStorage.getItem('project'));
```

### 2.6 环境变量

| 成员 | 说明 |
| --- | --- |
| `static getEnv(name)` / `static setEnv(name, value)` | 读写环境变量。 |
| `static get envs` | 全部环境变量。 |
| `getBrowserEnv()` | `'UE大屏'` 或 `'浏览器'`。 |

环境变量可在对象 JSON 中以 `${varName}` 插值（如 url 里的 `${earthsdk3-assets-script-dir}`）。

---

## 三、SceneObjectsManager（内部对象集合）

`objm.sceneObjectsManager` 持有全部被管理对象的集合。通常通过 `objm.createSceneObject*` 间接操作，也可直接访问。

> 完整属性/方法/事件见 `managers/SceneObjectsManager.md`。

| 成员 | 说明 |
| --- | --- |
| `get sceneObjects` | `Set<ESSceneObject>` 全部对象集合。 |
| `addSceneObject(obj)` | 添加（已存在则警告并返回 false）。 |
| `deleteSceneObject(obj)` | 移除（不销毁，仅脱离管理）。 |
| `get sceneObjectsToChange` | `Listener<[toDels, toAdds]>` 对象增删事件。 |

> `objm.destroySceneObject` = `sceneObjectsManager.deleteSceneObject` + `obj.destroy()`。

---

## 四、ESViewer（视口）

`ESViewer` 是抽象类，具体实现为 `ESCesiumViewer` / `ESOlViewer` / `ESUeViewer`（在引擎适配包中）。通过 `objm.activeViewer` 或 `objm.createXxxViewer` 取得。

> 完整属性/方法/事件见 `managers/ESViewer.md`。

### 4.1 场景对象管理（引擎自动，开发者一般不直接调用）

对象的加入/移除由 `ViewersManager` **自动驱动**：`objm.createSceneObject*` 创建对象时自动 `add` 到所有匹配 `devTags` 的视口，`destroySceneObject` 时自动 `delete`。下列 `ESViewer` 方法主要由 `ViewersManager` 内部调用，开发者通常无需手动调；仅在需要临时挂载或特殊控制时才用到 `disposableAdd` 等。

| 方法 | 说明 |
| --- | --- |
| `add(...objs)` | 将对象加入视口（按"视口类型 + 对象类型"映射创建 `EngineObject` 并渲染）。引擎自动调。 |
| `delete(...objs)` | 从视口移除（销毁引擎对象）。引擎自动调。 |
| `disposableAdd(...objs)` / `disAdd(...)` | 临时加入并返回"移除函数"，调用即移除（适合临时挂载的辅助对象）。 |
| `has(obj)` | 视口中是否包含该对象。 |
| `clearAllSceneObjects()` | 清空视口内全部对象。 |
| `getEngineObject(sceneObject)` | 取对象对应的引擎层 `EngineObject`（Cesium/UE/OL 具体渲染对象）。 |
| `get sceneObjects` | 视口内对象迭代器。 |

> **devTags 过滤**：viewer 与 sceneObject 都可设置 `devTags`（字符串数组）限定对象只渲染到指定视口（多视口场景）。双方 `devTags` 为空/`undefined` 时全部匹配（默认行为，对象自动进所有视口）。

```ts
// 临时挂载辅助对象（用完即移除），不纳入 SceneObjectsManager 托管
const don = viewer.disposableAdd(helperObj);
// …
don(); // 移除
```

### 4.2 相机

| 方法 | 说明 |
| --- | --- |
| `flyIn(position, rotation?, duration?, flyMode?)` | 飞行到指定位置/姿态。 |
| `flyTo(flyToParam, position, flyMode?)` | 按飞行参数飞行。 |
| `flyToBoundingSphere(rectangle, distance?, duration?)` | 飞到边界球。 |
| `getCurrentCameraInfo()` | 取当前相机 `{position, rotation}`。 |
| `transformFlyParam(position, flyParam)` | 飞行参数转换。 |
| `getLengthInPixel()` | 像素对应长度。 |
| `getBoundSphere(id)` | 取对象边界球。 |
| `getCurrentRectangle()` | 取当前视口矩形 `[west,south,east,north]`。 |

```ts
viewer.flyIn([116.39, 39.9, 500], [0, -90, 0], 2); // 飞到北京上空，朝下，2秒
const cam = viewer.getCurrentCameraInfo();
```

### 4.3 拾取与高度

| 方法 | 说明 |
| --- | --- |
| `pick(screenPosition, attachedInfo?, parentInfo?)` | 屏幕坐标拾取（返回 `ESJPickedResult`）。 |
| `pickPosition(screenPosition)` | 屏幕坐标拾取三维坐标。 |
| `quickPickPosition(screenPosition)` | 快速拾取（Cesium 下不触发 3DTileset preUpdate，性能高）。 |
| `getTerrainHeight(position)` | 取地形高度。 |
| `getHeightByLonLat(lon, lat, channel?)` | 经纬度取高度（UE 可指定通道）。 |
| `getHeightsByLonLats(lonLats, channel?)` | 批量取高度。 |
| `lonLatAltToScreenPosition(position)` | 经纬高转屏幕坐标。 |

```ts
viewer.clickEvent.don(async (e) => {
  const pos = await viewer.pickPosition(e.screenPosition);
  console.log('点击处坐标', pos);
});
```

### 4.4 导航模式

| 方法 | 说明 |
| --- | --- |
| `changeToMap()` | 切换到地图模式。 |
| `changeToWalk(position, jumpZVelocity?, eyeHeight?)` | 步行模式。 |
| `changeToRotateGlobe(latitude?, height?, cycleTime?)` | 旋转地球模式。 |
| `changeToLine(geoLineStringId, speed?, heightOffset?, loop?, turnRateDPS?, lineMode?)` | 沿线路径模式。 |
| `changeToRotatePoint(position, distance?, orbitPeriod?, heading?, pitch?)` | 绕点旋转。 |
| `changeToFollow(objectId, distance?, heading?, pitch?, relativeRotation?)` | 跟随对象。 |
| `changeToUserDefined(userDefinedPawn)` | 自定义 Pawn（UE）。 |
| `getNavigationMode()` / `get navigationMode` | 当前导航模式。 |

```ts
// 跟随人员对象
viewer.changeToFollow('ESHumanTest', 20);
// 绕点旋转
viewer.changeToRotatePoint([116.39, 39.9, 1], 300);
```

### 4.5 编辑

| 方法 | 说明 |
| --- | --- |
| `startEditing(sceneObject, modes, options?)` | 开始编辑。`modes` 可为字符串或 `ESJEditingMode` 枚举数组。 |
| `stopEditing()` | 停止编辑。 |
| `moveObjects(sceneObjects)` | 整体移动多个对象。 |
| `get editingEvent` | 编辑事件（start/changed/end）。 |

```ts
const obj = objm.createSceneObject('ESImageLabel');
obj.position = [116.23, 23.45, 30];
viewer.startEditing(obj, 'translation'); // 平移编辑
// …
viewer.stopEditing();
```

`modes` 取值（`ESJEditingMode`）：`SinglePoint`、`Translation`、`Rotation`、`Scale`、`DoublePoints`、`LineStringAppend`、`LineStringInsert`、`CircularAppend`、`HeightModify`、`VisibilityAppend` 等。

### 4.6 事件

所有事件均为 `Event` / `Listener`，用 `.don(handler)` 订阅，返回解绑函数。

| 事件 | 载荷 | 说明 |
| --- | --- | --- |
| `clickEvent` | `{screenPosition?, pointerEvent?}` | 单击 |
| `dblclickEvent` | `{screenPosition?, pointerEvent?}` | 双击 |
| `hoverEvent` | `{screenPosition, pointerEvent?}` | 悬停（时长由 `hoverTime` 控制） |
| `pointerOverEvent` / `pointerOutEvent` | `{screenPosition, pointerEvent?}` | 指针进入/移出 |
| `pointerMoveEvent` | `{screenPosition, pointerEvent?}` | 指针移动 |
| `pointerDownEvent` / `pointerUpEvent` | `{screenPosition, pointerEvent?}` | 指针按下/抬起 |
| `keyDownEvent` / `keyUpEvent` | `KeyboardEvent` | 键盘 |
| `wheelEvent` | `WheelEvent` | 滚轮 |
| `cameraChanged` | - | 相机变化 |
| `viewerChanged` | `innerViewer` | 视口内部重建 |
| `navigationEvent` | `{mode, info:{position,index}}` | 导航事件（仅 Line 模式） |

```ts
const don = viewer.clickEvent.don((e) => {
  console.log('屏幕坐标', e.screenPosition);
});
// 取消订阅
don();
```

### 4.7 截图与状态

| 方法/属性 | 说明 |
| --- | --- |
| `capture(resx?, resy?)` | 截图，返回 base64 Promise。 |
| `getFPS()` | 帧率。 |
| `getVersion()` | 版本信息。 |
| `get status` / `setStatus(...)` | 视口状态（`'Raw'` 等）。 |
| `get statusLog` / `setStatusLog(...)` | 状态日志。 |
| `get container` / `set container` | 容器元素。 |
| `set containerOrId` | 按 id 或元素设置容器。 |
| `get containerSize` | 容器尺寸。 |
| `get actived` / `set actived` | 是否激活。 |
| `forceRecreate()` | 强制重建视口。 |

### 4.8 常用属性（节选，均响应式）

通过 `viewer.xxx = value` 设置，`viewer.xxxChanged` 监听变化。完整列表见源码 `ESViewer.createCommonProps`。

| 属性 | 说明 |
| --- | --- |
| `globeShow` | 是否显示地球。 |
| `fov` | 视椎体夹角（默认 60）。 |
| `currentTime` / `simulationTime` | 当前时间 / 仿真时间（控制光照与动画）。 |
| `timeSync` | 时间同步开关。 |
| `setCurrentTime(value)` | 设置当前时间（数字毫秒或字符串）。 |
| `rain` / `snow` / `cloud` / `fog` / `depthOfField` | 天气与景深强度（0~1）。 |
| `atmosphere` | 大气效果。 |
| `terrainOpacity` / `depthTestAgainstTerrain` | 地形透明度 / 深度检测。 |
| `splitPosition` / `rollerShutter` | 卷帘位置 / 是否开启卷帘。 |
| `cameraMovableRegion` | 相机可移动区域。 |
| `lonLatFormat` | 经纬度格式。 |
| `ionAccessToken` | Cesium Ion 令牌。 |
| `editingPointSize` / `editingLineColor` / … | 编辑辅助样式。 |
| `sceneBackgroundColor` / `sceneGlobeBaseColor` | 背景色 / 地球基底色。 |

---

## 五、对象-视口自动关联机制（EngineObject）

理解这一节是理解 earthsdk3 的关键：**开发者只管创建/配置场景对象，渲染由引擎自动完成**。

### 5.1 自动关联链路

```
objm.createSceneObjectFromJson(json)
  └─> SceneObjectsManager.addSceneObject(obj)        // 纳入 _sceneObjects 集合
        └─> 触发 sceneObjectsToChange 事件
              └─> ViewersManager 监听
                    └─> 对每个匹配 devTags 的 viewer：
                          viewer.add(obj)
                            └─> EngineObjectsContext.createEngineObject(obj, viewer)
                                  └─> 按 (viewer.typeName, obj.typeName) 查注册表
                                        └─> 创建 EngineObject（Cesium/UE/OL 具体渲染对象）
```

- **`SceneObjectsManager`**（`objm.sceneObjectsManager`）：持有全部被托管对象的集合，对象增删时触发 `sceneObjectsToChange`。
- **`ViewersManager`**（`objm` 内部）：监听 `sceneObjectsToChange` 与 `viewersChanged`，自动把对象 `add` 到所有匹配视口；新视口创建时也会自动把现有对象全部加入。
- **`EngineObject`**（引擎适配层）：每个 `ESSceneObject` 被加入视口时，由 `EngineObjectsContext` 按"视口类型 + 对象类型"映射创建一个 `EngineObject`，它是该对象在该引擎中的具体渲染实现（如 Cesium 的 Entity/Primitive）。开发者**不直接创建或接触** `EngineObject`，引擎适配包（`earthsdk3-cesium` 等）在导入时完成注册。

### 5.2 devTags 多视口过滤

`viewer.devTags` 与 `sceneObject.devTags`（字符串数组）用于多视口场景限定对象渲染范围：

- 任一方 `devTags` 为空/`undefined` -> 全部匹配（默认，对象进所有视口）。
- 双方都非空 -> 需有交集标签才渲染到该视口。

### 5.3 开发者需要做什么

- **创建对象**：`objm.createSceneObjectFromJson(...)` 或 `objm.sceneTree.createSceneObjectTreeItemFromJson(...)`。
- **配置属性**：`obj.position = [...]` 等，响应式自动同步到引擎层。
- **销毁对象**：`objm.destroySceneObject(obj)`（自动从所有视口移除并销毁引擎对象）。
- **不需要**：手动 `viewer.add` / `viewer.delete`（由 `ViewersManager` 自动调）、创建 `EngineObject`。

> `viewer.add` / `delete` / `clearAllSceneObjects` 等 API 虽 public，但属引擎内部机制，仅临时挂载（`disposableAdd`）或高级控制时才用。

---

## 六、场景对象通用模式

### 6.1 创建：优先用 JSON

`createSceneObjectFromJson` 是最推荐的方式——对象 JSON 可序列化、可持久化、可在三引擎间通用。

```ts
const obj = objm.createSceneObjectFromJson({
  id: 'my-model',           // 可选，不填自动生成
  type: 'ESGltfModel',
  name: '大楼',
  position: [116.39, 39.9, 0],
  rotation: [0, 0, 0],
  url: '${earthsdk3-assets-script-dir}/assets/glb/building.glb',
});
```

> 每个对象类型的 JSON 示例见其 `objects/<类名>.md` 的"JSON 示例"节。

### 6.2 属性：响应式读写

所有 `createDefaultProps` 声明的属性都是响应式字段，直接 `obj.prop = value` 即可；用 `obj.propChanged.don(fn)` 监听变化。

```ts
model.position = [116.40, 39.9, 0];        // 写
const pos = model.position;                 // 读
model.positionChanged.don((v) => {          // 监听
  console.log('位置变了', v);
});
```

### 6.3 对象生命周期与自动渲染

对象一旦通过 `objm.createSceneObject*` 或 `sceneTree.createSceneObjectTreeItem*` 创建，即自动纳入 `SceneObjectsManager` 托管并**自动渲染**到所有匹配 `devTags` 的视口，**无需手动 `viewer.add`**：

- **创建即渲染**：`createSceneObjectFromJson` 内部 `addSceneObject` 触发 `sceneObjectsToChange`，`ViewersManager` 自动 `viewer.add` 并创建 `EngineObject`。
- **销毁即移除**：`objm.destroySceneObject(obj)` 自动从所有视口 `delete` 并销毁引擎对象。
- **场景树方式**：`sceneTree.createSceneObjectTreeItemFromJson(...)` 创建对象并挂到场景树节点，对象同样自动渲染，且节点管理其生命周期、可被场景树 UI 编辑/拖拽。这是 demo 与编辑器最常用的方式。
- **多视口过滤**：通过 `devTags` 限定对象只渲染到特定视口。

```ts
// 方式一：直接创建（自动渲染）
const model = objm.createSceneObjectFromJson({ type: 'ESGltfModel', position: [116.39, 39.9, 0], url: '...' });

// 方式二：通过场景树创建（自动渲染 + 节点管理 + UI 可编辑），最常用
const treeItem = objm.sceneTree.createSceneObjectTreeItemFromJson({
  sceneObject: { type: 'ES3DTileset', name: '工厂', url: '...' },
});

// 销毁（自动从视口移除）
objm.destroySceneObject(model);
```

### 6.4 事件

对象事件分两类：
- **属性变化**：`xxxChanged`（如 `positionChanged`）。
- **业务事件**：`xxxEvent`（如 `pickedEvent`、`smoothMoveEvent`）。

```ts
model.pickedEvent.don((info) => { /* 被点击 */ });
```

### 6.5 方法调用

对象方法直接调用，如 `model.smoothMove([116.40, 39.9, 0], 2)`（2 秒平滑移动）。各对象可用方法见其 `objects/<类名>.md` 的"方法"表，含继承方法（来源列标注定义类）。

### 6.6 序列化

单个对象：

```ts
const json = model.json;        // 不含默认值
const full = model.completeJson;// 含默认值
```

整个项目：用 `objm.json` / `objm.completeJson`（见 2.5）。

### 6.7 销毁

```ts
objm.destroySceneObject(model); // 从管理器移除并销毁
```

---

## 七、常用代码模式合集

### 7.1 初始化（Cesium）

```ts
import { ESObjectsManager } from 'earthsdk3';
import 'earthsdk3-cesium';

const objm = new ESObjectsManager();
const viewer = objm.createCesiumViewer(document.getElementById('container')!);
```

### 7.2 加载 3DTileset 并飞行定位

```ts
const tileset = objm.createSceneObjectFromJson({
  type: 'ES3DTileset',
  name: '工厂',
  url: 'http://localhost:9004/tileset.json',
});
tileset.flyTo(2); // 2 秒飞行定位（ESSceneObject 通用方法）
```

### 7.3 点击拾取坐标

```ts
viewer.clickEvent.don(async (e) => {
  if (!e.screenPosition) return;
  const pos = await viewer.pickPosition(e.screenPosition);
  if (pos) console.log('经纬高', pos);
});
```

### 7.4 绘制折线并测距

```ts
const line = objm.createSceneObjectFromJson({
  type: 'ESGeoLineString',
  points: [[116.39, 39.9, 0], [116.40, 39.9, 0], [116.40, 39.91, 0]],
});
console.log('总距离', line.getDistance()); // ESGeoVector 方法
```

### 7.5 平滑移动人员并跟随

```ts
const human = objm.createSceneObjectFromJson({
  id: 'worker1',
  type: 'ESHuman',
  position: [116.39, 39.9, 0.5],
  animation: 'walking',
});
viewer.changeToFollow('worker1', 20);
human.smoothMove([116.40, 39.9, 0.5], 20); // 20 秒移动
```

### 7.6 开启编辑（平移/旋转/缩放）

```ts
viewer.startEditing(model, ['Translation', 'Rotation', 'Scale']);
// …
viewer.stopEditing();
```

### 7.7 切换引擎（Cesium ↔ UE）

```ts
// 视角与属性自动同步
objm.switchToUEViewer(container, 'http://localhost:9007/', 'earthsdk3');
// …
objm.switchToCesiumViewer(container);
```

### 7.8 项目保存与恢复

```ts
localStorage.setItem('proj', JSON.stringify(objm.json));
// 下次
objm.json = JSON.parse(localStorage.getItem('proj')!);
```

---

## 八、更多

- **某对象的完整属性/方法/事件**：查 `objects/<类名>.md`（如 `objects/ESGltfModel.md`）。
- **管理器类完整 API**：`ESObjectsManager`/`SceneObjectsManager`/`ViewersManager`/`ESViewer`/`SceneTree` 等管理器的属性/方法/事件见 `managers/<类名>.md`（如 `managers/ESObjectsManager.md`），索引见 `managers/index.md`。
- **不知道选哪个对象**：查 `layer2-classification.md` 按分类选型。
- **MCP/AI 工具**：`ESMCPTools` 暴露相机与对象操作工具供大模型驱动，详见源码 `ESMCPTools/tools/`。
- **类型系统**：`ESJTypes` 定义可序列化属性类型（`ESJVector3D`、`ESJColor`、`ESJFillStyle`、`ESJResource`、`ESJEditingMode` 等），完整定义见 `reference/types/index.md`（按子模块拆分）。
