# 要素查询 / 几何

> 分类代号：C.3  本章共 **4 个接口**

本章汇集所有"拿到几何 / 属性 / 投影"的基础操作。其中 `queryFeatures` 是最常用的接口，根据 `querytype` 可覆盖点/矩形/表达式/条件 4 种查询场景，因此 SDK 只暴露一个 `query_features(querytype=...)` 统一入口。

---

## 快速索引

| 序号 | SDK 方法 | HTTP | URL |
|----:|---------|------|-----|
| 26 | `query_features` / `queryFeatures` | POST | `/api/v1/map/cmd/queryFeatures/{mapid}/{version}` |
| 27 | `create_entity_geo_data` / `createEntityGeoData` | POST | `/api/v1/map/cmd/createEntityGeoData/_null/v1` |
| 28 | `coord_transform` / `coordTransform` | POST | `/api/v1/map/cmd/coordtransform/_/_` |
| 29 | `prj_wkt_to_prj4_str` / `prjWktToPrj4Str` | POST | `/api/v1/map/cmd/prjWktToPrj4Str/_/_` |

---

## 26 · 要素查询 `queryFeatures`

**URL**：`POST /api/v1/map/cmd/queryFeatures/{mapid}/{version}`  
**SDK**：`svc.query_features(querytype, ...)` / `svc.queryFeatures({ querytype, ... })`  

### `querytype` 枚举

| 值 | 含义 | 必填参数 | 适用打开方式 |
|----|------|---------|------------|
| `point` | 点查询：在屏幕像素范围内找要素 | `x`, `y`, `pixelsize`, `zoom`, `pixelToGeoLength` | 两种模式均可 |
| `rect` | 矩形查询：几何 bounds 内找要素 | `x1`, `y1`, `x2`, `y2` | 两种模式均可 |
| `expresion` | 表达式查询：按 DWG 属性字段过滤 | `expr` | 两种模式均可 |
| `condition` | 条件查询：按后端条件（SQL 片段）过滤 | `condition`（可空），可选 `bounds` | **仅 GeomRender**（几何库）；路由到内存进程时回 `not support query type!`（见下方 `geom` 说明） |

> ⚠️ `expresion` 是后端拼写，**不是** `expression`。

> ⚠️ **`limit`/`beginpos` 分页（GeomRender）**：`rect`（有无 bounds）+ `limit:3` 返回 3 条，不传 limit 默认上限 100；`condition` + `limit`/`beginpos` 正常翻页。0 命中时返回 `{}`（无 `recordCount`/`result` 键），不是 `{recordCount:0}`。`point` 查询**不传** `maxReturnCount` 时后端强制按 1 处理（传了则按所传值）。

> ⚠️ 图形引擎部分响应 `Content-Type` 不规范（octet-stream 里实为 JSON）。SDK 已按首字符嗅探自动解析；手写 fetch 时注意不能只看 content-type。

### 可选参数（所有 querytype 通用）

| 字段 | 说明 |
|------|------|
| `limit` | 最大返回条数（condition/expresion 分支消费；point/rect 用 `maxReturnCount`，默认 100） |
| `beginpos` | 起始偏移（condition/expresion 分页） |
| `fields` | 要返回的属性字段，逗号分隔，空串 = 全部 |
| `simplifyTolerance` | 几何简化容差（默认 0 = 不简化；>0 走 `ST_Simplify`） |
| `useCache` | 开启查询缓存（condition/expresion 常用） |
| `toMapCoordinate` | `true` 时把 `geojson` 转成地图坐标系并附 `isMapCoordinate:true`（默认 false，返回 CAD 坐标） |
| `maxGeomBytesSize` | 单条几何字节上限（默认 100000，超过则降级为 envelop） |
| `layername` | 指定 style/layer 查询；不传则自动取第一个 `isSys && isGeom` 样式 |
| `includegeom` / `realgeom` | condition 查询的几何返回开关（`includegeom` 含几何、`realgeom` 返回真实几何而非切片简化版） |
| `bounds` | condition 查询的空间范围，**必须是字符串**：`"x1,y1,x2,y2"` / `"[x1,y1,x2,y2]"`（矩形，方括号会被剥掉），或 `"x,y;x,y;..."`（含 `;` = 折线，首尾点相同 = 多边形）。后端 `cmdparam.value("bounds").toString()`——传 JSON 数组会变成空串，空间过滤被**静默丢弃**；后端也不读 `linePts`。SDK 已把点数组 `[[x,y],…]` 转成 `"x,y;x,y"`、4 数数组转成 `"[x1,y1,x2,y2]"` |
| `isContains` / `isGetIntersections` | condition 查询的空间关系选项（配合上面的线/面 `bounds`） |
| `condition` | 也可叠加在 point/rect 上追加 SQL 过滤（后端会剥离 `delete/update/insert/exec/truncate` 关键字） |

> 注意：point/rect 分支不读 `limit`，要用 `maxReturnCount`。`geom` 不是"返回几何"的开关（那是 `includegeom`/`realgeom`），而是**命令路由开关**：缺省按该图的打开方式路由；`geom:true` 走几何库进程；`geom:false` 强制走内存进程——内存进程只实现 point/rect/expresion，**condition 一律回 `{"error":"not support query type!"}`**。所以**从未以 GeomRender（`geom:true`）打开过**的图（缺省路由看 mapinfos ini 里该版本的 `geom` 标记；旧版 SDK 因发 `mapopenway` 而非 `geom`，新建的图都是这样）做不了 condition 查询；一般不传 `geom`，图按 GeomRender 打开过即可。

### 响应结构

```json
{
  "recordCount": 5,
  "result": [
    {
      "id": 1,
      "objectid": "EA_ED__EE_#",
      "layerindex": 13,
      "name": "AcDbArc",
      "geojson": "...",
      "envelop": "POLYGON(...)",
      "bounds": "[minx,miny,maxx,maxy]",
      "isEnvelop": true,
      "color": -65281,
      "linetype": "BYLAYER",
      "xdata": "",
      "center": "x,y,z",
      "points": "x1,y1,z1;x2,y2,z2;...",
      "colorIndex": 6
    }
  ]
}
```

- `recordCount`：**后端实际扫到的全量数量**，不是本次返回条数。想分页时根据 `recordCount` + `beginpos` 自行控制。
- `geojson` 是字符串而非对象，调用方需要再 `JSON.parse()`。
- `isEnvelop=true` 说明 geojson 被替换成了 envelop（单条太大或命中 `maxGeomBytesSize`）。

### 实采（rect 查询 sys_zp/v1，limit=5）

```json
{
  "recordCount": 5,
  "result": [{
    "id": 1,
    "objectid": "EA_ED__EE_#",
    "layerindex": 13,
    "name": "AcDbArc",
    "geojson": "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Polygon\",\"coordinates\":[[[2271440.45,-685837.25],[2271440.45,-679783.61],[2276451.33,-679783.61],[2276451.33,-685837.25],[2271440.45,-685837.25]]]}]}",
    "envelop": "POLYGON((2271440.45 -685837.25, ... ))",
    "bounds": "[587661158.58,3103885970.22,587661256.00,3103886087.91]",
    "isEnvelop": true,
    "color": -65281,
    "linetype": "BYLAYER"
  }]
}
```

### 实采（condition 查询，limit=1）

```json
{
  "recordCount": 6982,
  "result": [{
    "id": 1,
    "name": "AcDbArc",
    "geojson": "",
    "envelop": "POLYGON(...)",
    "bounds": "[...]",
    "isEnvelop": false,
    "color": -65281,
    "center": "587661197.39,3103886029.19,-152738.04",
    "points": "x1,y1,z1;x2,y2,z2;x3,y3,z3",
    "colorIndex": 6
  }]
}
```

`recordCount=6982` 表示该地图共 6982 个要素满足条件；`limit=1` 只取第一条。

### Python

```python
# 矩形查询：sys_zp/v1 全图前 5 条
meta = svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender)
x1, y1, x2, y2 = [float(x) for x in meta["bounds"][1:-1].split(",")][:4]
res = svc.query_features(
    querytype="rect",
    mapid="sys_zp", version="v1",
    x1=x1, y1=y1, x2=x2, y2=y2,
    limit=5,
)
for item in res["result"]:
    print(item["id"], item["name"])

# 点查询：屏幕 5 像素范围
res = svc.query_features(
    querytype="point",
    x=587661197.39, y=3103886029.19,
    zoom=10,
    pixelsize=5,
    pixel_to_geo_length=100.0,
    limit=3,
)
```

### Node.js

```javascript
const res = await svc.queryFeatures({
  querytype: 'rect',
  mapid: 'sys_zp', version: 'v1',
  x1, y1, x2, y2, limit: 5,
});

const byExpr = await svc.queryFeatures({
  querytype: 'expresion',
  expr: "layerindex=13",
  limit: 10,
});
```

### curl

```bash
curl -X POST "http://127.0.0.1:27660/api/v1/map/cmd/queryFeatures/sys_zp/v1?token=$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"querytype":"condition","layername":"","zoom":1,"limit":1}'
```

---

## 27 · 构造实体几何 `createEntityGeoData`

**URL**：`POST /api/v1/map/cmd/createEntityGeoData/_null/v1`  
**SDK**：`svc.create_entity_geo_data(mapdata=..., map_extent=..., render_accuracy=..., exclude_attribute=..., use_zip=...)`

> `mapdata` 是前端绘图模块构造的 DWG 结构体（JSON 字符串）。独立使用场景较少，通常和前端绘图配合。后端特判：`mapdata` 内含 `"isSvgData":` 时会先走 `imageSvgToDwg` 把 SVG 转成 DWG 结构再生成几何。

### 请求参数

| 字段 | 类型 | 说明 |
|------|------|------|
| `mapdata` | string | 前端传入的 DWG JSON 字符串 |
| `mapExtent` | string | 地图范围 JSON：`[minx,miny,maxx,maxy]` |
| `renderAccuracy` | number | 渲染精度（像素） |
| `excludeAttribute` | bool | 是否排除属性（SDK 默认 true；后端缺省 false） |
| `useZip` | bool | 是否对 mapdata 做 gzip 压缩（默认 true） |

### 响应

- 合法输入：`{ "result": [...], "bounds": "...", ... }`（与 queryFeatures 类似）

---

## 28 · 坐标转换 `coordtransform`

**URL**：`POST /api/v1/map/cmd/coordtransform/_/_`  
**SDK**：`svc.coord_transform(srs=..., crs=..., points=..., four_parameter=..., is_inverse_four_parameter=...)`

### 请求参数

| 字段 | 类型 | 说明 |
|------|------|------|
| `srs` | string | 源坐标系，例如 `EPSG:4326` |
| `crs` | string | 目标坐标系，例如 `EPSG:3857` |
| `points` | string | 多点串：`x1,y1;x2,y2` |
| `fourParameter` | string | 可选四参数：`xOff,yOff,scale,rotRad` |
| `isInverseFourParamter` | bool | 是否反向应用四参数（注意后端拼写是 `Paramter`） |

SDK 的 `points` 接受：

- 单点 `(x,y)` 或 `{"x":1,"y":2}`
- 多点 `[(x1,y1),(x2,y2)]`
- `fourParameter` 支持 `"x,y,s,r"` 或 `[x,y,s,r]`

### 响应（实采：4326 → 3857 两点）

```json
{
  "points": [
    [12957254.769864663, 4852582.082864688],
    [13522423.824622115, 3662655.1833045874]
  ]
}
```

### Python

```python
res = svc.coord_transform(
    srs="EPSG:4326",
    crs="EPSG:3857",
    points=[(116.397, 39.908), (121.474, 31.23)],
)
print(res["points"])  # [[12957254.77, 4852582.08], [13522423.82, 3662655.18]]
```

### Node.js

```javascript
const res = await svc.coordTransform({
  srs: 'EPSG:4326',
  crs: 'EPSG:3857',
  points: [[116.397, 39.908], [121.474, 31.23]],
});
console.log(res.points);
```

---

## 29 · WKT → PROJ4 `prjWktToPrj4Str`

**URL**：`POST /api/v1/map/cmd/prjWktToPrj4Str/_/_`  
**SDK**：`svc.prj_wkt_to_prj4_str(wkt, from_="")`

### 请求参数

| 字段 | 类型 | 说明 |
|------|------|------|
| `wkt` | string | ESRI prj 文件内容或 WKT 字符串 |
| `from` | string | 数据来源。空串 = WKT；其他可选 `wmsauto / xml / urn / crsurl / url / micoordsys / pci` |

### 响应（实采：WGS84 WKT）

```json
{ "proj4": "+proj=longlat +datum=WGS84 +no_defs" }
```

### Python

```python
wkt = open("sample.prj", encoding="utf-8").read()
res = svc.prj_wkt_to_prj4_str(wkt)
print(res["proj4"])
```

### Node.js

```javascript
import { readFileSync } from 'node:fs';
const wkt = readFileSync('sample.prj', 'utf8');
const res = await svc.prjWktToPrj4Str(wkt);
console.log(res.proj4);
```

---
