> **本文回答**：`res.bounds` → `GeoBounds` → `GeoProjection` / `LnglatProjection` 的链路，以及 `map.fitMapBounds`、`map.getGeoBounds`、`updateMapExtent` 的用法。

## 一、数据流

```
svc.openMap(...) --> res.bounds (string)
       |
       v
 vjmap.GeoBounds.fromString(res.bounds)  // 得到 GeoBounds 实例
       |
       v
 new vjmap.GeoProjection(mapExtent)       // 几何投影：CAD → 渲染经纬度
       |
       v
 new vjmap.Map({center: prj.toLngLat(...), zoom, ...})
 map.attach(svc, prj)
```

## 二、GeoBounds 常用方法

```js
const extent = vjmap.GeoBounds.fromString(res.bounds);
// 或者自定义范围：
const extent2 = vjmap.GeoBounds.fromArray([x1, y1, x2, y2]);

extent.center();             // 中心点（CAD 坐标）
extent.width();              // 宽度
extent.height();             // 高度
extent.toPointArray();       // 四个角点
extent.randomPoint();        // 随机取一个点（常用于批量生成演示数据）
extent.randomPoints(minCount, maxCount);  // ⚠️ 两参必填：随机取 min~max 个点（d.ts 签名核实）
extent.square();             // 按较长边补齐成正方形（新建图前常用）
extent.toString();           // 序列化为字符串
```

## 三、两种投影对象

### GeoProjection（CAD 图为底图）

```js
const prj = new vjmap.GeoProjection(mapExtent);
const lngLat = prj.toLngLat([cadX, cadY]);
const cadPt  = prj.fromLngLat(lngLat);
prj.getMapExtent();          // 返回 GeoBounds
```

### LnglatProjection（互联网地图为底图）

```js
const prj = new vjmap.LnglatProjection();
// 经纬度与"渲染经纬度"是一一对应：
const lngLat = prj.toLngLat([116.391, 39.907]);  // 输出仍是 [116.391, 39.907]
```

## 四、Map 上的便捷 API

```js
await map.onLoad();

map.fitMapBounds();          // 缩放至图范围
map.getGeoBounds();          // 全图范围 × initViewScale（缺省 0.7），要 1 倍传 getGeoBounds(1)
map.getGeoBounds(0.4);       // 0.4 倍范围（CAD 有效内容大致范围）
map.getGeoBounds(0.6);       // 0.6 倍（取点时常用）

// CAD <-> 渲染经纬度（attach 了 prj 后，map 与 prj 等价）
map.toLngLat([cadX, cadY]);
map.fromLngLat([lng, lat]);

// 屏幕像素 <-> 经纬度
map.project(lngLat);         // 返回 {x, y}
map.unproject([x, y]);       // 返回 LngLat
```

> **`fitBounds` vs `fitMapBounds`**：`fitBounds(bounds)` 只吃 **LngLatLike**（经纬度 bounds），把 CAD `GeoBounds` 直接传给它会抛 `` `LngLatLike` argument must be specified as ... `` 类型错误。CAD 坐标系的 bounds 一律用 `fitMapBounds(GeoBounds | number)`，内部会自动 `toLngLat` 转换再调 `fitBounds`。

## 五、精确获取有效图形范围

`res.bounds` 往往是整张 drawing 的扩展范围，可能包含空白。两种方式取"有实体的区域"：

```js
// 方式 1：范围准确、速度稍慢（发一次后端请求；Memory 方式打开的图恒返回 null，须判空回退方式 2）
const dataBounds = await svc.cmdGetDrawBounds();

// 方式 2：范围大致准确、速度快（本地缓存）
const drawBounds = svc.currentMapParam().drawBounds;
```

## 六、动态改变图范围

生成图形过程中范围会扩大（如 vjmapext 绘图），需要让 prj + map 重新计算：

```js
map.updateMapExtent(newBoundsString);  // 内部会重建 GeoProjection
map.fitMapBounds();
```

`updateMapExtent` 常在 "前端数据 -> 后端 `cmdCreateEntitiesGeomData` -> 返回新 mapBounds" 的回流过程中调用。

## 七、常见坑

1. **忘了把 `res.bounds` 传给 `GeoProjection`** → 后续 `toLngLat` 全是 NaN。
2. **直接把 `res.bounds` 作为数组传**（它是字符串 `"[x1,y1,x2,y2]"`）→ 用 `fromString`，或直接传构造函数也支持字符串。
3. **`map.getGeoBounds()` 当作 CAD 真实范围** → 它返回的是全图范围 × `initViewScale`（缺省 0.7）；要真实绘图范围用 `svc.cmdGetDrawBounds`（Memory 图返回 null，回退 `currentMapParam().drawBounds`）。
4. **互联网底图用了 `GeoProjection`** → 应该用 `LnglatProjection`。否则坐标会错位。

## 八、GeoBounds 工具方法与同名函数辨析

`vjmap.getGeoBounds(data)`（顶层函数）与 `map.getGeoBounds(ratio)`（地图方法）**同名不同物**：

- `vjmap.getGeoBounds(data)`：计算 GeoJSON / 点数组 / **JSON 字符串**的地理范围 → `GeoBounds`。字符串入参走 `JSON.parse`，**纯文本 `"x,y,x,y"` 抛 `Invalid input coordinate`**——文本格式用 `GeoBounds.fromString("x,y,x,y")`（自动补 `[]`）。
- `map.getGeoBounds(ratio?)`：全图范围 × ratio（见上文）。

GeoBounds 的语义：

| 方法 | 行为 |
|---|---|
| `closestPoint(pt, includeInterior?)` | 界外点→最近边界点；内部点默认返回自身，`includeInterior=true` 也返回自身（不投影到边） |
| `square()` | 返回包容正方形 |
| `scale(r)` | 以中心缩放，`scale(2)` → 各边外扩一倍 |
| `translate(dx,dy)` | **原地平移**（改 this，不 clone） |
| `isIntersect(b)` / `isContains(b)` / `contains(pt)` | 边界相交/包含关系 |
| `randomPoint()` / `randomGeoJsonPointCollection(n)` / `LineCollection` / `PolygonCollection` | 界内随机生成 GeoJSON |
