> **本文回答**：`mapcadLayer.queryMapEntities` / `queryMapEntitiesByLayer` —— 一条 SQL 从后端拉回批量 DWG 实体。

## 一、为什么要用它

直接用 `svc.conditionQueryFeature` 有三个坑：

1. **类型名是数字 ID**：后端表里 `name='13'` 代表 `AcDbText`，不是字符串。
2. **分批请求贵**：一次 HTTP + SQL 空间查询有几十 ms 开销；N 次查询 = N × 开销。
3. **结果需要再处理**：坐标系、缺省字段处理。

`queryMapEntities` 封装了这三点：

```js
const fc = await mapcadLayer.queryMapEntities({
  entTypes: ['AcDbText', 'AcDbMText'],     // 自动翻译成 name='13' or name='12'
  condition: "text like '%标题%'"          // 再 AND 拼 where
});
```

返回标准 `GeoJSON.FeatureCollection`。

## 二、queryMapEntities 选项

| 选项 | 类型 | 说明 |
|---|---|---|
| `entTypes` | `string[]` | AcDb 类型名数组；SDK 翻译并 or 拼入 where |
| `condition` | `string` | 额外 SQL where；与 entTypes 以 AND 组合 |
| `bounds` | `[x1,y1,x2,y2]` 或点数组 | 范围限定 |
| `isContains` | `boolean` | 范围查询是包含 (`true`) 还是相交 (`false`，默认) |
| `fields` | `string` | 逗号分隔字段名 |
| `limit` | `number` | 单次上限，默认 50000 |
| `beginpos` | `number` | 分页起点 |
| `toMapCoordinate` | `boolean` | 返回 CAD 坐标（默认 `true`）；false = Mercator |
| `includegeom` / `realgeom` | `boolean` | 是否返回完整 geojson 几何（`coordType=1` 时默认 true） |
| `coordType` | `0 \| 1` | `0`=只要位置坐标（轻量），`1`=真实几何，默认 `1` |
| `layer` | `string` | 样式图层名过滤（对应 IQueryBaseFeatures.layer；按图层查更推荐用 `queryMapEntitiesByLayer`） |
| `zoom` | `number` | 单次查询 zoom |
| `mapid` / `version` | `string` | 指定图/版本（默认当前打开的） |
| `extra` | `Record<string,any>` | 透传给 `conditionQueryFeature` 的其它参数 |

## 三、常用 entTypes 翻译表

| 类型名 | ID |
|---|---|
| `AcDbLine` | 1 |
| `AcDbPolyline` | 2 |
| `AcDb2dPolyline` | 3 |
| `AcDb3dPolyline` | 4 |
| `AcDbSpline` | 5 |
| `AcDbArc` | 6 |
| `AcDbCircle` | 7 |
| `AcDbEllipse` | 8 |
| `AcDbBlockReference` | 10 |
| `AcDbHatch` | 11 |
| `AcDbMText` | 12 |
| `AcDbText` | 13 |
| `AcDbAttributeDefinition` | 26 |
| `AcDbAttribute` | 27 |

翻译过程 SDK 会调 `svc.getConstData().entTypeIdMap` 并按 service 缓存。

## 四、按图层查询

```js
const fc = await mapcadLayer.queryMapEntitiesByLayer(
  '道路',                                   // 图层名（不是索引）
  ['AcDbLine', 'AcDbPolyline', 'AcDb2dPolyline'],
  { limit: 20000 }
);
// 内部 where: layerindex=N and (name='1' or name='2' or name='3')
```

`entType` 传 `undefined` → 只按图层过滤。

> ⚠️ **图层名 vs 索引坑**（实采 2026-09-20）：`layerName` 是在 `svc.getMapLayers()` 里按 `l.name` 匹配的**图层名**；查询结果里 `properties.layerindex` 是数字索引（如 `12`），把这个索引串当 layerName 传回去会查不到图层并**静默返回空 FC**（不报错）。实体属性里的图层名要用 `getMapLayers()[layerindex].name` 反查。

## 五、combineWhere 拼多条件

```js
const where = vjmapext.combineWhere(
  "text like '%A%'",
  "text like '%B%'",
  ""                         // 空串自动忽略
);
// → (text like '%A%') or (text like '%B%')
```

## 六、buildEntTypeCondition 手动拿类型片段

自己拼 SQL：

```js
const typeFrag = await vjmapext.buildEntTypeCondition(map, ['AcDbText','AcDbMText']);
// → "name='13' or name='12'"

const fc = await mapcadLayer.queryMapEntities({
  condition: `(${typeFrag}) and color=-65536`
});
```

## 七、分页

单次 `limit` 最大建议 50000；大数据分页：

```js
async function fetchAll(opts, limit = 10000) {
  const all = [];
  for (let begin = 0; ; begin += limit) {      // 游标按 limit 步进，不能按 features.length
    const fc = await mapcadLayer.queryMapEntities({ ...opts, limit, beginpos: begin });
    all.push(...fc.features);
    if (fc.eof || begin + limit >= (fc.recordCount ?? 0)) break;   // 以 eof / recordCount 判结束
  }
  return all;
}
```

⚠️ 无法提取几何（既无 geom 也无 location）的记录会在组装 FeatureCollection 时被**丢弃**，所以 `features.length` 可能小于本页实际行数——用它判断"最后一页"会提前退出，用它推进 `beginpos` 会重复取数。

## 八、返回结构

```ts
{
  type: 'FeatureCollection',
  features: Feature[],
  recordCount?: number,      // 总数
  beginpos?: number,
  eof?: boolean
}
```

每个 Feature：

```ts
{
  type: 'Feature',
  id: string | number,              // objectid
  properties: {
    name: string,                   // 'AcDbText' 等可读名
    objectid: string,
    layerindex: number,
    color: number,                  // int32，可能负数
    location?: string,              // "x,y" 或 "x,y;x,y"
    points?: string,
    text?: string, contents?: string,
    height?: number, rotate?: number,
    ...
  },
  geometry: Geometry
}
```

## 九、典型场景

### 1 找出所有 "总平面图" 文字

```js
const fc = await mapcadLayer.queryMapEntities({
  entTypes: ['AcDbText', 'AcDbMText'],
  condition: vjmapext.combineWhere(
    "text like '%总平面图%'",
    "contents like '%总平面图%'"
  )
});
```

### 2 取视口内某图层所有线

```js
const bounds = map.getGeoBounds();
const fc = await mapcadLayer.queryMapEntitiesByLayer('wall', ['AcDbLine','AcDbPolyline'], {
  bounds: [bounds.min.x, bounds.min.y, bounds.max.x, bounds.max.y],
  isContains: false
});
```

### 3 仅要属性不要几何（速度提升）

```js
const fc = await mapcadLayer.queryMapEntities({
  entTypes: ['AcDbText'],
  fields: 'objectid,text,location,color,layerindex',
  includegeom: false
});
```

## 十、常见坑

1. **entTypes 直接传 `'AcDbText'`**：`queryMapEntities` 的 `entTypes` **必须传数组** `['AcDbText']`——单字符串会被逐字符当类型名查表，全部不命中，**静默返回空 FeatureCollection**。只有 `queryMapEntitiesByLayer` 的 `entType` 形参容忍单字符串（内部自动归一为数组）。
2. **bounds 单位**：是 CAD 坐标，不是经纬度；要 `map.fromLngLat` 转。
3. **limit 过大 + includegeom**：返回数据 MB 级，网络和 parse 都慢。
4. **name='AcDbText' 永远命中 0 条**：后端 `name` 是数字 ID，不是可读名。
5. **多个 `queryMapEntities` 并发**：OK，但服务端有 rate limit；超 30 req/s 可能被限流。
6. **`fields` 必须含 `geojson` 才出 Feature**：行转 Feature 时 SDK 只认 `geom/points/location/center/insertionPoint/origin` 等几何字段，而 `geom` 本身是 `_processQueryResult` 从后端 `geojson` 字段合成的——`fields:"objectid"` 这类纯属性清单会让 `recordCount=554` 但 `features` **为 0**（行全被丢，不报错）。`fields` 里写 `geojson` 而非 `geom`。

## 相关

- 后续接管 → [features-to-entities](features-to-entities.md)
- 隐藏原图 → [mapdata-hider](mapdata-hider.md)
- SQL 表达式细节 → [backend/query-sql](../../../vjmap/references/backend/query-sql.md)
