> **本文回答**：`svc.openMap` 成功但 `res.bounds` 读不到 / 投影建立失败 —— bounds 格式解析错。

## 一、症状

```js
const res = await svc.openMap({ mapid: 'xxx' });
console.log(res.bounds);
// 字符串 "[0,0,1000,2000]"（带方括号，minX,minY,maxX,maxY）

const prj = new vjmap.GeoProjection(JSON.parse(res.bounds));   // 传数组 / {min,max} 普通对象
// TypeError: this.mapExtent.width is not a function
// res.bounds 为 undefined（openMap 失败）时：Cannot read properties of undefined (reading 'width')
```

## 二、根因

`res.bounds` 是**带方括号的字符串** `"[minX,minY,maxX,maxY]"`；`GeoProjection` 只认 `GeoBounds` 实例（或这种字符串，构造内部会 `fromString`），数组 / 普通对象都会在 `setExtent` 里调 `.width()` 时崩：

| 形式 | 来源 | 如何解析 |
|---|---|---|
| `"[0,0,1000,2000]"` | 服务端返回（openMap 的 `res.bounds`） | `vjmap.GeoBounds.fromString(res.bounds)` |
| `"0,0,1000,2000"` | 无方括号（如 `GeoBounds.toString()` 产物） | 同上（fromString 自动补 `[]` 再 JSON.parse） |
| `GeoBounds` 对象 | 已解析 | 直接传 GeoProjection |
| `undefined` | openMap 失败 | 先查 `res.error` |

注意：`GeoBounds.fromString` 要求**扁平四元素**数组——`"[[0,0],[1000,2000]]"` 这种嵌套对会抛 `param is not array length four string`。

## 三、健壮解析

```js
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);   // 官方统一姿势
const prj = new vjmap.GeoProjection(mapExtent);
```

## 四、openMap 返回结构

```js
const res = await svc.openMap({ mapid: 'xxx', mapopenway: vjmap.MapOpenWay.GeomRender });
// res = {
//   error?: string,        // 失败时带上
//   bounds?: string,       // "[minX,minY,maxX,maxY]"
//   mapid, ... (元数据)
// }
```

`bounds` 是 openMap 成功后**保证有**的字段（字符串）。响应里没有 `ext_geomrect` 字段。

## 五、建议代码模板

```js
const svc = new vjmap.Service(url, token);

const res = await svc.openMap({ mapid, mapopenway: vjmap.MapOpenWay.GeomRender });
if (res.error) throw new Error('openMap 失败：' + res.error);

// GeoBounds.fromString 同时兼容 "x1,y1,x2,y2" 与 "[x1,y1,x2,y2]"
const mapExtent = vjmap.GeoBounds.fromString(res.bounds);
const prj = new vjmap.GeoProjection(mapExtent);
const map = new vjmap.Map({ container: 'map' });
map.attach(svc, prj);
await map.onLoad();
```

补充：`new vjmap.GeoProjection(res.bounds)` 在**运行时**也能工作——构造函数内部判断 `typeof extent === 'string'` 时会自动 `GeoBounds.fromString`。但 d.ts 只声明 `GeoBounds` 类型，TS 工程建议显式转换。

## 六、验证 bounds 正确性

```js
console.log('bounds:', mapExtent);
// GeoBounds { min: GeoPoint{x,y}, max: GeoPoint{x,y} }
console.log(mapExtent.toString()); // "0,0,1000,2000"
```

- min / max 都有 x / y
- max > min
- 不是全 0 或 NaN

坐标一般是 CAD 内部坐标；如果 max - min = 0 → 图是空的或 bounds 读错。

## 七、为什么投影需要 bounds

`GeoProjection` 用 bounds 决定：

- 地图 0/0 经纬度对应 CAD 哪个点
- 每 meter 对应多少 CAD 单位

bounds 不对 → 整张图位置 / 比例尺错。

## 八、空白地图也需要 bounds

```js
// 必须传 GeoBounds 实例——{min:{x,y},max:{x,y}} 普通对象会抛 this.mapExtent.width is not a function
const prj = new vjmap.GeoProjection(vjmap.GeoBounds.fromArray([0, 0, 1000, 1000]));

const map = new vjmap.Map({ container: 'map' });
map.attach(null, prj);     // 不连后端
```

空白地图自己定 bounds。

## 九、动态改 bounds

用 `map.updateMapExtent(newBounds)`（收字符串 / 数组 / GeoBounds）重建投影，再 `map.fitMapBounds()`；不必 `map.remove` 重建。注意它只换投影、不换瓦片源——要切到另一张 CAD 图用 `map.switchMap`。

## 十、常见坑

1. **`res.bounds` 直接 JSON.parse**：得到的是扁平 `[x1,y1,x2,y2]` 数组，`GeoProjection` 用不了；必须 `GeoBounds.fromString` 或 new GeoProjection 传原字符串。
2. **写成嵌套 `[[0,0],[1000,2000]]`**：`fromString` 要扁平四元素，嵌套对会抛 `param is not array length four string`。
3. **复制粘贴**：某些示例用 `bounds: res.bounds` 直接；不通用，按本文模板兜底。
4. **空白地图 bounds 太大**：几百万的范围 → 精度损失；限制在合理范围。
5. **bounds 的 Y 轴方向**：vjmap 通常 Y 向上；传反了图上下翻转。

## 相关

- 打开图 → [open-map/open-dwg](../open-map/open-dwg.md)
- 投影 → [open-map/bounds-and-projection](../open-map/bounds-and-projection.md)
- 坐标转换 → [coord/overview](../coord/overview.md)
