# 地图服务后端SDK接口及下载

vjmap 后端 REST 接口文档 — 面向二次开发者的版本(vjmapservice/)。

后端SDK下载pythonnodejsswagger服务。下载地址:http://vjmap.com/download/vjmapservice.zip (opens new window)

image-20260425205121562

# 目录


# 00-overview.md

# 概述(Overview)

# 1. 产品定位

vjmap 是一套以 CAD/GIS 图档为中心的服务端系统,核心能力包括:

  • 上传 DWG / DXF / 图片等源文件,在服务端解析成数据库
  • 栅格瓦片MVT 矢量瓦片的形式提供 Web 地图访问
  • 基于 cmd 指令完成元数据查询、样式切换、几何分析、表格/中心线提取、文件导出等
  • 存储用户 KV 数据、工作区、符号库、全文索引等辅助能力
  • 支持 Python / Node.js SDK 直接对接;

典型使用场景:

  • 将单位内部 DWG 档案转成 Web 可浏览地图
  • 需要把 CAD 图与 WMS / MVT 图层混合使用
  • 针对 DWG 做图形分析(比较、表格提取、中心线)并导出 PDF / DXF
  • 在业务系统里存储轻量 KV 配置或自定义属性

# 2. 系统架构

┌────────────────┐   JWT      ┌──────────────────────┐   stdin/stdout   ┌──────────────────────────┐
│ Client (Web/   │ ─────────► │ web服务       │ ───────────────► │ 图形引擎服务     │
│ Python / Node) │            │ :27660               │                  │ geom / render / export    │
└────────────────┘ ◄───────── │ router + handler  │ ◄─────────────── │ appCmd / mapPsCmd / ...   │
        ▲                     │ + 缓存 / 限流 / 权限 │                  │  持久化        │
        │ REST / HTTP         └──────────────────────┘                  └──────────────────────────┘
        │                              │
        │                              ├── Workspace(多租户命名空间)
        │                              ├── Full-text search
        │                              ├── 符号库
        │                              └── AI / 向量检索(可选)
        │
        └── Tile 通过 CDN / HTTP 缓存直接给前端
1
2
3
4
5
6
7
8
9
10
11
12
13
  • web服务(:入口;做 JWT 鉴权、路由转发、缓存清理、工作区路由改写。
  • **图形服务 **:实际的 CAD 解析/渲染/分析。
  • 数据存储:每张图对应一个 数据库文件(默认 data/maps/{hash}.db),元数据、样式、缓存瓦片都放在里面;另有全局 KV、工作区、符号库等独立存储。
  • 可选 AI服务:/ai/*/aimcp/*/aiagent/* 三套子服务通过反代挂载;不是所有部署都启用。

# 3. 常用接口分 13 大类

# 分类 典型接口 文档
1 地图管理 upload / openmap / metadata / listmaps / thumbnail api/01-map-management.md
2 瓦片/样式 tile / mvt / wms / createMapStyle / switchlayer api/02-tiles-and-styles.md
3 要素查询 queryFeatures / createEntityGeoData api/03-query.md
4 图形分析 mapDiff / mapCompare / extractTable / centerLines api/04-analysis.md
5 组合/导出 composeNewMap / exportPdf / exportDxf api/05-compose-and-export.md
6 WebCAD listWebcadDraws / saveWebcadPatch(可选) api/06-webcad.md
7 工作区 getWorkspace / workspaceCreate api/07-workspace.md
8 KV 存储 data/{key} / datas / datakeys api/08-data-storage.md
9 全文搜索 fullsearch/search / add / delete api/09-fullsearch.md
10 符号库 symbol/categories / symbol/list api/10-symbols.md
11 系统 heart / version / runstatus / viewlogs api/11-system.md
12 /service 杂项 qrcode / fonts / uploadImg api/12-service-misc.md
13 AI(可选) vectorSearch / chatFaq / vectorDocs api/13-ai.md

# 4. 该怎么读这份文档?

按兴趣从下面挑一条路径:

  1. 第一次接触 vjmap:先过 01-getting-started.md 把 Python 或 Node SDK 跑起来。
  2. 看得懂 curl 就够:直接翻 api/01-map-management.md,里面每个接口都有 curl + Python + Node 三列。

# 5. 命名与术语

术语 含义
mapid 图唯一 ID;通常是业务名或 _ 占位
version 图版本号;默认 v1
layer 图层名;多数 cmd 通过请求体 layers 字段指定
fileid 源文件 ID,一般等于 md5 前缀
secretKey / accessKey 加密图的访问密钥(前者 16 字符,后者 ak 开头 18 字符)
workspace 命名工作区;默认工作区等价于 ""(空)
geom render 图形服务子进程,负责几何渲染/切片

# 6. 约定(Conventions)速览

  • 所有 /api/v1/map/... 接口需要 token=<JWT>(query 或 header)
  • 95 % 的 cmd 接口既接受 GET 也接受 POST;SDK 统一走 POST
  • 成功响应结构两类:
    • 网关包装 {code:0, msg:"", data:{...}}
    • 图形服务 JSON(直接是 {...} 或数组);SDK 自动识别并 unwrap
  • 错误:HTTP 非 2xx → 抛 VjmapError;网关 {code≠0} 也视为错误抛出
  • 详细规则见 02-conventions.md

# 01-getting-started.md

# 快速上手(Getting Started)

在 5 分钟内用 Python 或 Node.js SDK 跑通 vjmap 的最小闭环。

# 1. 前置条件

  • 已部署的 vjmap 服务,默认运行在 http://127.0.0.1:27660
  • 一个有效 JWT Token(开发环境可直接从后端控制台获取;生产请走登录接口)
  • Python ≥ 3.8 Node.js ≥ 18

# 2. 健康检查

服务是否活着:

curl -s http://127.0.0.1:27660/heart
# => {"status":"ok","timestamp":"..."}

curl -s http://127.0.0.1:27660/version
# => {"arch":"amd64","os":"windows","status":"ok","time":"...","version":"..."}
1
2
3
4
5

# 3. Python 一分钟体验

# 1. 拷贝 SDK
cp sdk/python/vjmap.py your-project/

# 2. 装依赖
pip install requests

# 3. hello world
python - <<'PY'
from vjmap import Vjmap
svc = Vjmap("http://127.0.0.1:27660/api/v1", "<paste-token>")
print("heart   ->", svc.heart())
print("version ->", svc.version())
print("status  ->", svc.run_status(detail=True)["application"])
PY
1
2
3
4
5
6
7
8
9
10
11
12
13
14

期望输出:

heart   -> {'status': 'ok', 'timestamp': '...'}
version -> {'arch': 'amd64', 'os': 'windows', 'status': 'ok', 'time': '...', 'version': '2026041001'}
status  -> {'pid': '...', 'runtime': ..., ...}
1
2
3

# 4. Node.js 一分钟体验

cp sdk/nodejs/vjmap.js your-project/
node - <<'JS'
const { Vjmap } = require('./vjmap');
(async () => {
  const svc = new Vjmap('http://127.0.0.1:27660/api/v1', '<paste-token>');
  console.log('heart   ->', await svc.heart());
  console.log('version ->', await svc.version());
  console.log('status  ->', (await svc.runStatus()).application);
})();
JS
1
2
3
4
5
6
7
8
9
10

# 5. 最常用的 4 步闭环

下面示例用 Python;Node 版本见 examples/nodejs/

# 5.1 列出已有的图

maps = svc.list_maps()           # 对应 /api/v1/map/cmd/listmaps/_/v1
for mapid, versions in maps.items():
    print(mapid, [v["version"] for v in versions])
1
2
3

# 5.2 打开图并读元数据

svc.open_map(mapid="sys_zp", version="v1", mapopenway="GeomRender")
meta = svc.metadata()
print("layers:", [l["name"] for l in meta.get("layers", [])])
print("bounds:", meta.get("bounds"))
1
2
3
4

# 5.3 取栅格瓦片 URL( XYZ source)

url = svc.raster_tile_url(mapid="sys_zp", version="v1",
                          layer="0,1,2,3,4,5,6,7,8", z="{z}", x="{x}", y="{y}")
print(url)
# => http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/0,1,.../{z}/{x}/{y}?token=...
1
2
3
4

把 URL 直接填到 vjmap 的 raster source 就能显示。

# 5.4 关闭图(可选,释放 图形服务 子进程)

svc.close_map(mapid="sys_zp", version="v1")
1

# 6. 下一步


# 02-conventions.md

# 通用约定(Conventions)

所有接口的共性规则集中在这里,具体接口文档不再重复。

# 1. Base URL 与前缀

  • 默认:http://127.0.0.1:27660/api/v1
  • 文档里写成 {base};SDK 自动补 / 结尾
  • 三种子前缀:
前缀 用途
{base}/map/... 默认工作区的所有图相关接口
{base}/workspace/{name}/... 命名工作区下的图相关接口(与 /map/ 等价但隔离存储)
{base}/service/... 与图无关的工作区/KV/符号库/全文搜索/AI 等接口

系统级根路径:

路径 说明
/heart 健康检查
/version 版本

# 2. HTTP 方法

  • cmd 接口(路径形如 /api/v1/map/cmd/<cmd>/<mapid>/<version>既接受 GET 也接受 POST;网关 Router.Any 统一挂载。SDK 默认走 POST,原因:
    1. 请求体大时 GET 超长
    2. 语义上是"动作"而非"只读资源"
  • 其它接口遵循标准 REST:GET 查、POST 创建、PUT 修改、DELETE 删除

# 3. 鉴权

详情见 03-authentication.md

  • 必带token=<JWT>(推荐放 query,也可放 header)
  • 加密图:请求头 secretKey: <key1>_;_<key2>... 或 query secret_key=...
  • 全局 access keyak 开头 18 位):可放在 accessKey 位置用来一次性访问多张加密图

# 4. 路径占位符

占位 常用取值 含义
{mapid} sys_zp__null 图 ID;_ / _null 用在不需要 mapid 的 cmd 上
{version} v1_ 版本号;_ 表示与具体版本无关
{layer} "0,1,2"default、空 图层名/ID;多个用逗号
{z}/{x}/{y} 10/854/402 标准 XYZ 瓦片坐标
{key} 用户自定义 KV 存储的 key(建议 URL-safe)

重要:SDK 会对占位符做 encodeURIComponent,别再手动编码。

# 5. 请求体约定

场景 Content-Type 备注
绝大多数 cmd application/json SDK 默认
文件上传(/map/uploads/service/uploadImg multipart/form-data 字段名 file
KV 存值 POST /data/{key} application/x-www-form-urlencoded 字段 data=

# 6. 响应结构

两类并存,SDK 都能识别:

# 6.1 web服务 网关标准响应

{
  "code": 0,
  "msg": "",
  "data": { /* 实际内容 */ }
}
1
2
3
4
5
  • code == 0 → SDK unwrap 成 data
  • code != 0 → SDK 抛 VjmapError(status=200, body={...})

# 6.2 图形服务

{
  "bounds": [...],
  "layers": [...],
  ...
}
1
2
3
4
5

直接返回,不包 {code,msg,data}

# 6.3 错误响应

  • HTTP 4xx / 5xx:SDK 抛 VjmapError(status=4xx|5xx, url, body)
  • cmd 执行失败但网关路由成功:多数 cmd 用 {error: "..."}{result: {succeed: false}} 表达;SDK 不会自动判断业务成功/失败,请业务侧自行校验

# 7. 分页 / 列表

大部分列出接口没有分页,一次返回全部:

  • listmaps:按 mapid 聚合,返回 {mapid: [ {version, db, filename, ...} ]}
  • symbol/list/{cat}:数组
  • getWorkspace:数组

少数有 limit / offset / pageSize / pageNum(例如 fullsearch/search),每个接口文档单独说明。

# 8. 幂等性与重试

操作 幂等 可自动重试?
openmap (GeomRender) 否,会占用 图形服务 子进程
metadata / listmaps / runstatus
updatemap / updateMetadata
deletemap / deletestyle 是,但业务需防误触
瓦片 GET

SDK 不内置重试;

# 9. 超时

  • SDK 默认 60 秒;超长 cmd(composeNewMapsaveOfflineTileDbextractTable on 大图)可能要 3-5 分钟,调用时显式 timeout=300(秒)。
  • 图形服务单请求默认 600000 ms = 10 minrunstatus.process.reqTimeout),超过会被强杀。

# 10. 文件下载 / 流式接口

导出 PDF、DXF、离线瓦片包、缩略图等接口返回 二进制流。SDK 里对应方法:

  • Python:svc._request("POST", url, stream=True) 返回原始 requests.Response,自己 .iter_content() 写文件
  • Node:svc._request('POST', url, { stream: true }) 返回原始 Response,自己 await res.arrayBuffer() 或管道到文件

# 11. 命名风格(跨语言)

语言 风格
Python SDK snake_case open_map, close_map, raster_tile_url
Node SDK camelCase openMap, closeMap, rasterTileUrl
文档正文 二者并列列出 每个接口都给两种示例

# 12. 日志与可观测

  • runstatus?detail=true:看引擎 pid、活跃请求、子进程等
  • viewlogs?record=100:看最近 100 条日志;含 cmd 耗时、错误

# 03-authentication.md

# 鉴权与加密图(Authentication)

# 1. JWT Token

  • vjmap 后端用 标准 HS256 JWT 做鉴权
  • Claims 里包含 ID / Username / AuthorityId / BufferTime / exp / iss=vjmap
  • 过期时间:默认 BufferTime=86400 秒exp 远期刷新由 {UpdateTokenRoute} 负责
  • 生产环境拿 token 的方式:通过业务登录接口;开发环境可直接从 vjmap 后台复制

# 1.1 Token 放哪里

三种都接受,优先级从高到低

  1. HTTP Header:token: eyJ...
  2. Query:?token=eyJ...
  3. Form:token=eyJ...(仅个别接口)

SDK 默认走 Query(便于 curl 排障);如果你的反代会剥 query,可传 header=True(Python)/{headers:{token}} 客户端自行覆盖。

# 1.2 Token 的 5 分钟内测

TOKEN="eyJhbG..."
curl -s "http://127.0.0.1:27660/version?token=$TOKEN"
curl -s "http://127.0.0.1:27660/api/v1/map/cmd/runstatus/_/v1?token=$TOKEN&detail=true"
1
2
3

# 1.3 常见错误

现象 原因
HTTP 401 token 过期 / 签名不匹配
{code: 10006, msg: "..."}(网关响应) token 合法但权限不够(例如不是 root)
HTTP 200 但业务字段异常 token OK,但参数错误

# 2. 加密图(secretKey / accessKey)

vjmap 允许对图设置访问密钥,打开/瓦片/元数据请求都要带。

# 2.1 secretKey(每图一把,16 字符)

  • 创建/上传图时可通过 secretKey 字段设置
  • 明文密码 → secretKey 算法(与 Service.ts pwdToSecretKey 一致):
    • 连续 4 次 md5 后取前 16 字符
    • 空串 → 空串
    • 长度 == 18 且以 ak 开头 → 视为 accessKey,原样返回
  • SDK:compute_secret_key(pwd) / computeSecretKey(pwd)

# 2.2 accessKey(全局,18 字符 ak...

  • 跨图通用的"超级密钥",典型用途:服务端代为访问所有图
  • 放在请求头/query 里的字段也叫 secretKey,但值是 18 位 ak 串
  • SDK 识别:如果密钥串长度 == 18 且前缀 ak,不再 md5 拼装,直接透传

# 2.3 多密钥传递

一个请求可能同时携带多个 secretKey(覆盖多张图):

  • Header: secretKey: key1_;_key2_;_ak1234...abcd
  • Query: 用多个 secret_key= 或一个 secret_key=key1_;_key2

SDK Python:

svc._secret_keys = ["<16chars>", "<16chars>", "ak........"]
# 后续请求会自动拼 header
1
2

SDK Node:

svc._secretKeys = [ '16chars...', 'ak........' ];
1

# 2.4 加密图典型流程

svc = Vjmap(base, token)
svc._secret_keys = [compute_secret_key("myPass123")]
svc.open_map(mapid="finance_map", version="v1", mapopenway="GeomRender",
             secretKey="myPass123")   # 打开时也可带明文
meta = svc.metadata()                  # 会自动 carry secretKey
1
2
3
4
5

# 3. Super Key(运维视角)

.env 里的 VJMAP_MAP_SUPER_KEY 本质是一把 accessKey,含义是:

  • 整个实例的所有加密图都接受它
  • 二次开发一般不需要它;只有测试框架需要跑加密场景时才用

# 4. CSRF / 同源

  • 后端不强制 CSRF token;依赖 JWT 签名
  • 浏览器侧调用请注意 CORS:网关默认允许 Origin: *;如果在受限部署里碰到 CORS,需要运维修改 ReqMiddlewareCors

# 5. 速率限制

  • 默认无全局速率限制
  • 单个 cmd 的并发由 runstatus.maxParallelProcessGeomNum(几何渲染)控制,典型值 2-4
  • 超并发请求会被排队,不会报错但会"看起来卡住"

# 6. 过期 Token 的自动刷新

网关里有一个 {UpdateTokenRoute}(常见为 /api/v1/map/updateToken)。


# 04-workspace.md

# 工作区(Workspace)

工作区是 vjmap 的命名空间机制:不同工作区下的图、符号、KV 存储、全文索引互相隔离

# 1. 概念

  • 默认工作区:不带名字;URL 用 /api/v1/map/... 前缀
  • 命名工作区:人为创建;URL 变为 /api/v1/workspace/{name}/... 前缀
  • 工作区本身也有状态:{name, description, createTime, ...},由 /api/v1/service/getWorkspace 列出
  • 一个 token 理论上可访问所有工作区;实际权限由 AuthorityId 决定

# 2. 切换工作区(SDK)

# Python

svc = Vjmap("http://127.0.0.1:27660/api/v1", token)
svc.switch_workspace("team_a")          # 之后所有 map_url / cmd_url 都会自动带上 /workspace/team_a
svc.open_map("finance", "v1", mapopenway="Memory")  # → /api/v1/workspace/team_a/cmd/openmap/finance/v1
svc.switch_workspace("")                # 切回默认工作区
1
2
3
4

# Node

svc.switchWorkspace('team_a');
await svc.openMap({ mapid: 'finance', version: 'v1', mapopenway: 'Memory' });
svc.switchWorkspace('');
1
2
3

# 3. 生命周期接口

# 操作 URL 方法
1 列出 /api/v1/service/getWorkspace GET
2 创建 /api/v1/service/workspaceCreate POST
3 修改 /api/v1/service/workspaceModify POST
4 删除 /api/v1/service/workspaceDelete/{name} DELETE
5 状态 /api/v1/service/workspaceStatus GET

详细字段、响应示例请看 api/07-workspace.md

# 4. 底层 URL 改写规则

  • web服务网关把 /api/v1/workspace/{name}/* 改写成 带 workspace 参数/api/v1/map/*;图形服务不感知名字,由网关保证隔离
  • 因此凡是 /api/v1/map/ 下的路径都能在 /workspace/{name}/ 下一模一样使用
  • /api/v1/service/*(全局 KV、符号库、全文搜索)不受工作区前缀影响;它们的隔离通过各自的 workspace 参数实现(大部分接口接受 workspace= query)

# 5 反模式

  • 不要在热路径里频繁调用 switch_workspace() 再立刻发请求;SDK 内部只是改前缀,没 IO,但业务代码可能会混乱。建议一个 svc 实例绑一个 workspace
  • 不要把 workspacemapid 拼到一起当复合键用;保持每图一个 mapid,workspace 单独字段

# api/index.md

# 接口文档总索引(API Index)

本页把 13 个分类、73 个 接口分别映射到具体文档、SDK 方法。

# 0. 阅读顺序建议

  1. 先读 ../00-overview.md../02-conventions.md
  2. 再按需要翻下面分类文档
  3. 每个接口文档包含 4 块:URL & 方法 / 入参 / 出参 / 示例(curl + Python + Node + 真实响应)

# 1. 分类一览

# 分类 接口数 文档 Phase
01 地图管理(Map Management) 15 01-map-management.md C.1
02 瓦片 & 样式(Tiles & Styles) 10 02-tiles-and-styles.md C.2
03 要素查询(Query) 4 03-query.md C.3
04 图形分析(Analysis) 6 04-analysis.md C.4
05 组合 / 导出(Compose & Export) 6 05-compose-and-export.md C.5
06 WebCAD(可选) 6 06-webcad.md C.6
07 工作区(Workspace) 5 07-workspace.md C.7
08 KV 存储(Data Storage) 5 08-data-storage.md C.8
09 全文搜索(Full-text Search) 3 09-fullsearch.md C.9
10 符号库(Symbols) 3 10-symbols.md C.10
11 系统(System) 4 11-system.md C.11
12 /service 杂项(Service Misc) 3 12-service-misc.md C.12
13 AI(可选) 3 13-ai.md C.13
- 合计 73 - -

# 2. 快速找接口(常用任务 → 文档)

我要做…… 看这里
上传一个 DWG 文件并在后端渲染 01-map-management.md § uploads / openmap
读元数据(图层列表、范围) 01-map-management.md § metadata
生成 XYZ 瓦片 URL 02-tiles-and-styles.md § tile / mvt
切换显隐图层并应用样式 02-tiles-and-styles.md § createMapStyle / switchlayer
找几何要素 03-query.md § queryFeatures
两图对比 04-analysis.md § mapDiff / mapCompare
导出 PDF / DXF 05-compose-and-export.md
WebCAD 修改落盘 06-webcad.md
存一段用户配置 JSON 08-data-storage.md § data/{key}
全文搜索 09-fullsearch.md
看服务是否在运行 11-system.md § heart / runstatus

# 3. SDK 方法名速查

Python 与 Node 的方法名一一对应(snake_case ↔ camelCase):

Python Node 对应 URL
heart() heart() GET /heart
version() version() GET /version
run_status(detail) runStatus(detail) GET /api/v1/map/cmd/runstatus/_/v1
upload_map(...) uploadMap(...) POST /api/v1/map/uploads
check_map_file(md5) checkMapFile(md5) GET /api/v1/map/mapfile
open_map(mapid, version, ...) openMap({mapid, version, ...}) GET/POST /api/v1/map/openmap/{mapid}
update_map(...) updateMap(...) POST /api/v1/map/updatemap/{mapid}
close_map(mapid, version) closeMap(mapid, version) POST /api/v1/map/cmd/closemap/{mapid}/{version}
delete_map(mapid, version) deleteMap(mapid, version) POST /api/v1/map/cmd/deletemap/{mapid}/{version}
rename_map(old, new) renameMap(old, new) POST /api/v1/map/cmd/renamemap/{old}/_
list_maps() listMaps() POST /api/v1/map/cmd/listmaps/_/v1
metadata(mapid, version) metadata(mapid, version) POST /api/v1/map/cmd/metadata/{mapid}/{version}
update_metadata(...) updateMetadata(...) POST /api/v1/map/cmd/updateMetadata/{mapid}/{version}
thumbnail_url(...) thumbnailUrl(...) GET /api/v1/map/cmd/thumbnail/{mapid}/{version}
support_format() supportFormat() GET /api/v1/map/cmd/supportFormat/_/v1
const_data() constData() GET /api/v1/map/cmd/constData/_/v1
get_data_bounds(mapid, version) getDataBounds(mapid, version) POST /api/v1/map/cmd/getDataBounds/{mapid}/{version}
raster_tile_url(...) rasterTileUrl(...) GET /api/v1/map/tile/...
vector_tile_url(...) vectorTileUrl(...) GET /api/v1/map/tile/.../{z}/{x}/{y}.mvt
wms_tile_url(...) wmsTileUrl(...) GET /api/v1/map/cmd/wms/...
create_map_style(...) createMapStyle(...) POST /api/v1/map/cmd/createMapStyle/...
cmd_switch_layers(...) cmdSwitchLayers(...) POST /api/v1/map/cmd/switchlayer/...
cmd_update_style(...) cmdUpdateStyle(...) POST /api/v1/map/cmd/updateStyle/...
slice_layer(...) sliceLayer(...) POST /api/v1/map/cmd/slicelayer/...
get_slice_cache_zoom(...) getSliceCacheZoom(...) GET /api/v1/map/cmd/getSliceCacheZoom/...
delete_style(...) deleteStyle(...) POST /api/v1/map/cmd/deletestyle/...
query_features(...) queryFeatures(...) POST /api/v1/map/cmd/queryFeatures/...
create_entity_geo_data(...) createEntityGeoData(...) POST /api/v1/map/cmd/createEntityGeoData/_null/v1
coord_transform(...) coordTransform(...) POST /api/v1/map/cmd/coordtransform/_/_
prj_wkt_to_prj4_str(...) prjWktToPrj4Str(...) POST /api/v1/map/cmd/prjWktToPrj4Str/_/_
cmd_map_diff(...) cmdMapDiff(...) POST /api/v1/map/cmd/mapDiff/_null/v1
cmd_map_compare(...) cmdMapCompare(...) POST /api/v1/map/cmd/mapCompare/_null/v1
cmd_extract_table(...) cmdExtractTable(...) POST /api/v1/map/cmd/extractTable/_null/v1
cmd_extract_center_lines(...) cmdExtractCenterLines(...) POST /api/v1/map/cmd/extractCenterLines/_null/v1
cmd_match_object(...) cmdMatchObject(...) POST /api/v1/map/cmd/objectMatch/_null/v1
cmd_image_svg_to_dwg(...) cmdImageSvgToDwg(...) POST /api/v1/map/cmd/imageSvgToDwg/_null/v1
cmd_compose_new_map(...) cmdComposeNewMap(...) POST /api/v1/map/cmd/composeNewMap/_null/v1
cmd_split_map(...) cmdSplitMap(...) POST /api/v1/map/cmd/cmdSplitMap/_null/v1
cmd_export_layout(...) cmdExportLayout(...) POST /api/v1/map/cmd/exportLayout/_null/v1
cmd_export_pdf(...) cmdExportPdf(...) POST /api/v1/map/cmd/exportPdf/{mapid}/{version}
cmd_export_dxf(...) cmdExportDxf(...) POST /api/v1/map/cmd/exportDxf/{mapid}/{version}
cmd_save_offline_tile_db(...) cmdSaveOfflineTileDb(...) POST /api/v1/map/cmd/saveOfflineTileDb/_null/v1
list_webcad_draws() listWebcadDraws() GET /api/v1/map/cmd/listWebcadDraws/_/v1
get_webcad_data(...) getWebcadData(...) POST /api/v1/map/cmd/getWebcadData/...
save_webcad_patch(...) saveWebcadPatch(...) POST /api/v1/map/cmd/saveWebcadPatch/...
delete_webcad_draw(...) deleteWebcadDraw(...) POST /api/v1/map/cmd/deleteWebcadDraw/...
convert_webcad(...) convertWebcad(...) POST /api/v1/map/cmd/convertWebCAD/...
export_webcad(...) exportWebcad(...) POST /api/v1/map/cmd/exportWebCAD/_null/v1
get_workspaces() getWorkspaces() GET /api/v1/service/getWorkspace
workspace_create(...) workspaceCreate(...) POST /api/v1/service/workspaceCreate
workspace_modify(...) workspaceModify(...) POST /api/v1/service/workspaceModify
workspace_delete(name) workspaceDelete(name) DELETE /api/v1/service/workspaceDelete/{name}
workspace_status() workspaceStatus() GET /api/v1/service/workspaceStatus
get_custom_data(key) getCustomData(key) GET /api/v1/service/data/{key}
save_custom_data(key, val) saveCustomData(key, val) POST /api/v1/service/data/{key}
delete_custom_data(key) deleteCustomData(key) DELETE /api/v1/service/data/{key}
save_multiple_custom_data(...) saveMultipleCustomData(...) POST /api/v1/service/datas
get_custom_data_keys(prefix) getCustomDataKeys(prefix) GET /api/v1/service/datakeys
full_search(...) fullSearch(...) GET/POST /api/v1/service/fullsearch/search
full_search_add(...) fullSearchAdd(...) POST /api/v1/service/fullsearch/add
full_search_delete(...) fullSearchDelete(...) DELETE /api/v1/service/fullsearch/delete
symbol_categories() symbolCategories() GET /api/v1/service/symbol/categories
symbol_list(cat) symbolList(cat) GET /api/v1/service/symbol/list/{cat}
symbol_get(id) symbolGet(id) GET /api/v1/service/symbol/{id}
view_logs(record) viewLogs(record) GET /api/v1/map/cmd/viewlogs/_/v1
qrcode_url(content, size) qrcodeUrl(content, size) GET /api/v1/service/qrcode
glyphs_url(fontstack, range) glyphsUrl(fontstack, range) GET /api/v1/service/fonts/...
upload_img(file) uploadImg(file) POST /api/v1/service/uploadImg
vector_search(...) vectorSearch(...) POST /api/v1/service/vectorSearch
chat_faq(q) chatFaq(q) GET /api/v1/service/chatFaq
vector_docs(...) vectorDocs(...) GET /api/v1/service/vectorDocs

# api/01-map-management.md

# 地图管理(Map Management)

分类 1 / 13 · 接口 15 个 ·

快速目录

# 接口 方法 URL
1 秒传检测 GET /api/v1/map/mapfile
2 上传源文件 POST /api/v1/map/uploads
3 打开/创建地图 POST /api/v1/map/openmap/{mapid}
4 修改地图 POST /api/v1/map/updatemap/{mapid}
5 关闭地图 GET /api/v1/map/cmd/closemap/{mapid}/{version}
6 删除地图 GET /api/v1/map/cmd/deletemap/{mapid}/{version}
7 重命名地图 GET /api/v1/map/cmd/renamemap/{old}/_
8 删除源文件 GET /api/v1/map/cmd/deletemapfile/_/v1
9 列出地图 GET /api/v1/map/cmd/listmaps/_/v1
10 读元数据 GET /api/v1/map/cmd/metadata/{mapid}/{version}
11 修改元数据 POST /api/v1/map/cmd/updateMetadata/{mapid}/{version}
12 缩略图 GET /api/v1/map/cmd/thumbnail/{mapid}/{version}
13 支持的格式 GET /api/v1/map/cmd/supportFormat/_/v1
14 系统常量 GET /api/v1/map/cmd/constData/_/v1
15 数据范围 GET /api/v1/map/cmd/getDataBounds/{mapid}/{version}

共性约定(重要)

  • 所有 cmd/* 路径:GET 不需要 bodyPOST 要求 body 至少一个字段,否则后端回 500 param error。SDK 对无 body 的 cmd 统一走 GET。
  • openmap 通过 POST bodymapid/version/fileid/uploadname/mapopenwayfileid 必须来自 upload_map 的返回,不要自己拼 md5。
  • 所有路径都带 ?token=<JWT>(SDK 自动加)。

# 1. 秒传检测 check_map_file

URLGET /api/v1/map/mapfile?md5={md5}
用途:客户端上传前先算文件 md5,若 result:true 则复用已有文件,跳过上传。

# 入参(Query)

字段 类型 必填 说明
md5 string(32) 源文件 MD5(小写 hex)
token string JWT

# 出参

{"result": false}
1
  • result=true:已有同 md5 文件;可直接跳过上传用 fileid = md5[:12]
  • result=false:需要走 upload_map

# 示例

curl -s "http://127.0.0.1:27660/api/v1/map/mapfile?token=$T&md5=caf27de8a2259b2c4e04f2afde2f5c42"
1
svc.check_map_file("caf27de8a2259b2c4e04f2afde2f5c42")
1
await svc.checkMapFile('caf27de8a2259b2c4e04f2afde2f5c42');
1

# 2. 上传源文件 upload_map

URLPOST /api/v1/map/uploads
Content-Typemultipart/form-data

# 入参(Form)

字段 类型 必填 说明
file binary 源文件
md5 string(32) 预算的 MD5
secretKey string 设置加密图的 16 位密钥;SDK 中默认空

# 出参

{"fileid":"caf27de8a225","mapid":"caf27de8a225","uploadname":"caf27de8a225.dwg"}
1
  • fileid:生成的12 位十六进制(= md5 前 12 位)
  • mapid:默认等于 fileid;后续 open_map 可以给另一个业务名
  • uploadname:服务端实际落盘的文件名

# 示例

curl -s -X POST "http://127.0.0.1:27660/api/v1/map/uploads?token=$T" \
  -F "file=@./my.dwg" \
  -F "md5=$(md5sum my.dwg | awk '{print $1}')"
1
2
3
up = svc.upload_map("./my.dwg")
fileid, uploadname = up["fileid"], up["uploadname"]
1
2
const up = await svc.uploadMap('./my.dwg');
const { fileid, uploadname } = up;
1
2

# 3. 打开/创建地图 open_map

URLPOST /api/v1/map/openmap/{mapid}?version={version} (SDK 默认走 POST;GET 等价)

这是整套 API 的中心接口。它同时承担:

  • 首次创建:mapid 未存在时结合 fileid + uploadname 新建
  • 再次打开:mapid 已存在时忽略 fileid/uploadname
  • 切换模式:mapopenway 决定是否拉起 渲染子进程

# 入参(JSON body)

字段 类型 必填 说明
mapid string 业务 ID
version string 版本号,默认 v1
fileid string(12) 首次建图必填 upload_map 返回的 fileid
uploadname string 首次建图必填 upload_map 返回的 uploadname
mapopenway Memory / GeomRender ../02-conventions.md
style object 预设样式;详情见 § 2 create_map_style
secretKey string 图级密钥
accessKey string 全局 ak
mapbounds array / string 限制的图面范围

# 出参(节选,完整见 snapshot)

字段 含义
mapid / version / fileid / uploadname / filename 基本定位
bounds / dbBounds / drawBounds / initBounds / viewBounds 各种范围
layers (JSON string) 图层数组
styles (JSON string) 预置样式数组
format 瓦片格式(典型 png
mapopenway 实际使用的打开方式
pid 子进程 PID(GeomRender 时)
renderAccuracy 渲染精度
isnewmap 是否刚创建

# 真实响应片段

{
  "mapid": "nb_roundtrip_1776829368",
  "version": "v1",
  "fileid": "caf27de8a225",
  "uploadname": "caf27de8a225.dwg",
  "filename": "caf27de8a225.dwg",
  "mapopenway": "Memory",
  "bounds": "[67108.08576259,-10253.62403246,73355.58576721,-4006.12402784]",
  "isnewmap": true,
  "format": "png",
  "layers": "[{\"color\":\"#ffffff\",\"index\":0,...}]",
  "styles": "[{\"backcolor\":0,\"isgeom\":false,...}]"
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 示例

svc.open_map(
    mapid="demo_dwg", version="v1",
    mapopenway="Memory",
    fileid=up["fileid"], uploadname=up["uploadname"],
)
1
2
3
4
5
await svc.openMap({
  mapid: 'demo_dwg', version: 'v1',
  mapopenway: 'Memory',
  fileid: up.fileid, uploadname: up.uploadname,
});
1
2
3
4
5

# 4. 修改地图 update_map

URLPOST /api/v1/map/updatemap/{mapid}?version={version}

用法:修改 已存在图的属性(描述、可见性、部分字段等)。实现上等价于"带新参数再打开一次",因此 body 通常和 open_map 类似,加上要改的字段。

# 入参(JSON body)

字段 必填 说明
mapid 要更新的图
fileid 必须和打开时一致
uploadname 同上
mapopenway 同上
description 描述
deleteOldVersion 删除旧版本再建
…… 其余字段参考 ServiceInterface.ts::IUpdateMapParam

# 出参

结构与 open_map 返回一致。

# 示例

svc.update_map("demo_dwg", "v1", body={
    "mapid": "demo_dwg",
    "fileid": up["fileid"],
    "uploadname": up["uploadname"],
    "mapopenway": "Memory",
    "description": "添加二次开发示例描述",
})
1
2
3
4
5
6
7

# 5. 关闭地图 close_map

URLGET /api/v1/map/cmd/closemap/{mapid}/{version}

仅断开 图形服务 中的内存/渲染引用;不删除数据库文件,也不删图。

# 出参

{"status": true}
1

# 注意:POST 无 body 会 500

# ✗ 500 param error
curl -X POST ".../closemap/sys_zp/v1?token=$T"
# ✓ 200
curl ".../closemap/sys_zp/v1?token=$T"
# ✓ 200(POST 可以,但 body 必须有至少一个字段)
curl -X POST ".../closemap/sys_zp/v1?token=$T" -d '{"fileid":"caf27de8a225"}' -H 'content-type: application/json'
1
2
3
4
5
6

SDK 内部统一使用 GET 规避该陷阱。


# 6. 删除地图 delete_map

URLGET /api/v1/map/cmd/deletemap/{mapid}/{version}?keepSource={bool}

参数 默认 说明
keepSource true true 保留上传的源文件(只清元数据/DB),false 同时删源文件

# 出参

{"status": true}
1

# 示例

svc.delete_map("demo_dwg", "v1", keep_source=True)
1

# 7. 重命名地图 rename_map已知不稳定

URLGET /api/v1/map/cmd/renamemap/{old}/_?newmapid={new}

# 响应样例(实测)

{
  "status": false,
  "error": true,
  "reason": "mapid is empty",
  "newmapid": "xxx_new",
  "AuthorityId": "root"
}
1
2
3
4
5
6
7

# 8. 删除源文件 delete_map_file

URLGET /api/v1/map/cmd/deletemapfile/_/v1?files=a.dwg;b.dwg

删除上传到 data/mapfiles/ 目录下的物理文件。被删的文件必须没有图在引用

# 出参(实测)

{
  "deleted": [],
  "deletedCount": 0,
  "notFound": ["__nb_nonexistent__.dwg"],
  "status": false
}
1
2
3
4
5
6

# 示例

svc.delete_map_file(["caf27de8a225.dwg"])
# 或字符串
svc.delete_map_file("a.dwg;b.dwg")
1
2
3

# 9. 列出地图 list_maps

URLGET /api/v1/map/cmd/listmaps/_/v1

# 入参

Query 说明
keyword 按 mapid 过滤(可选)
page / pageSize 分页(可选,多数版本支持)

# 出参(实测)

{
  "sys_zp": [
    {
      "db": "c47be46a2ad2.db",
      "filename": "c47be46a2ad2.dwg",
      "geom": true,
      "mtime": "2026-04-10 11:31:00",
      "status": "finish",
      "uploadname": "sys_zp.dwg",
      "version": "v1"
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 顶层 key 是 mapid,value 是该图所有版本的数组
  • geom:true 表示该图已有切片缓存
  • status:"finish" 表示打开流程完成

# 示例

maps = svc.list_maps()
for mapid, versions in maps.items():
    print(mapid, [v["version"] for v in versions])
1
2
3
const maps = await svc.listMaps();
1

# 10. 读元数据 metadata

URLGET /api/v1/map/cmd/metadata/{mapid}/{version}

图必须先 open_map 打开才能读到完整元数据。

# 入参(Query)

字段 说明
enableEditMeta true 时返回可编辑字段;默认 false
fileid 罕见用法:读源文件级元数据

# 出参(节选)

字段非常多,以下是二次开发最常用的子集:

字段 类型 含义
mapid / name / version / fileid / uploadname / filename string 定位
bounds string("[minx,miny,maxx,maxy]") 整图范围
dbBounds / drawBounds / initBounds / viewBounds string 各阶段范围
createTime / time / mtime string 时间戳
layers JSON 字符串(数组) 图层列表
styles JSON 字符串(数组) 样式列表
layouts string,逗号分隔 布局名
findFonts / preferableFonts JSON/字符串 字体字典
geomRecordCount string 几何记录总数(估值)
ucsorg string UCS 原点矩阵
pid string 进程 PID(仅 GeomRender)

⚠ 注意:layers / styles / findFonts 等被序列化成字符串存在顶层;SDK 不会自动解嵌套 JSON,请按需 json.loads()

# 示例

svc.open_map("sys_zp", "v1", mapopenway="Memory",
             fileid="c47be46a2ad2", uploadname="sys_zp.dwg")
meta = svc.metadata("sys_zp", "v1")
layers = json.loads(meta["layers"])
1
2
3
4

# 11. 修改元数据 update_metadata

URLPOST /api/v1/map/cmd/updateMetadata/{mapid}/{version}

通过 POST body 修改图的"用户可编辑字段"(通常是 description、layerOn 状态、业务标签等)。

# 入参(JSON body)

{
  "fields": [
    { "name": "description", "value": "我是新的描述" },
    { "name": "tags",        "value": "building,sample" }
  ]
}
1
2
3
4
5
6

具体可编辑字段清单取决于后端版本;二次开发前可用 metadata?enableEditMeta=true 先探测。

# 出参

{"status": true}
1

# 示例

svc.update_metadata("demo_dwg", "v1", body={
    "fields": [{"name": "description", "value": "hello"}]
})
1
2
3

# 12. 缩略图 thumbnail

URLGET /api/v1/map/cmd/thumbnail/{mapid}/{version}?width=W&height=H[&layers=...][&backcolor=...]

返回 PNG 图片字节流。

# 常用参数

Query 说明
width / height 缩略图像素大小
layers 仅渲染指定图层;逗号分隔
backcolor 背景色(0xRRGGBB 数字或 #RRGGBB

# SDK

svc.open_map("sys_zp", "v1", mapopenway="GeomRender",
             fileid="c47be46a2ad2", uploadname="sys_zp.dwg")
png = svc.thumbnail_bytes("sys_zp", "v1", width=256, height=160)
open("thumb.png","wb").write(png)
1
2
3
4
const buf = await svc.thumbnailBytes('sys_zp', 'v1', { width: 256, height: 160 });
fs.writeFileSync('thumb.png', buf);
1
2

也可以只构造 URL 直接嵌到 <img src>

url = svc.thumbnail_url("sys_zp", "v1", width=128, height=96)
# => http://127.0.0.1:27660/api/v1/map/cmd/thumbnail/sys_zp/v1?width=128&height=96&token=...
1
2

# 13. 支持的格式 support_format

URLGET /api/v1/map/cmd/supportFormat/_/v1

# 实测响应

{
  "cad": "dwg;dxf",
  "image": "webp;heic;zip;png;pdf;jpg;jpe;jpc;heif;exif;avif;bmp;dib;tiff;hif;hdr;jfif;gif;tif;jpeg"
}
1
2
3
4

# 14. 系统常量 const_data

URLGET /api/v1/map/cmd/constData/_/v1

返回系统级字典,最常用的是 CAD 实体类型号 → AcDb 类名 的映射。

# 实测响应(节选)

{
  "entTypeIdMap": {
    "1":  "AcDbLine",
    "2":  "AcDbPolyline",
    "5":  "AcDbSpline",
    "6":  "AcDbArc",
    "7":  "AcDbCircle",
    "10": "AcDbBlockReference",
    "11": "AcDbHatch",
    "12": "AcDbMText",
    "13": "AcDbText",
    "34": "AcDb3dSolid"
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

搭配 query_featuresentType 过滤参数使用。


# 15. 数据范围 get_data_bounds

URLGET /api/v1/map/cmd/getDataBounds/{mapid}/{version}

返回图上所有 几何实体的紧凑包围盒,比 metadata.bounds(可能含空图框)更贴合可见数据。

# 实测响应

{
  "bounds": [
    587464241.0047014,
    3103794303.1750364,
    587769759.9828022,
    3104004303.209077
  ],
  "status": true
}
1
2
3
4
5
6
7
8
9

# 示例

b = svc.get_data_bounds("sys_zp", "v1")["bounds"]
# 用来设置 vjmap 初始 fitBounds
1
2

下一分类:02-tiles-and-styles.md


# api/02-tiles-styles.md

# 瓦片 / 样式

分类代号:C.2 本章共 10 个接口(3 个纯 URL 拼装 + 7 个 REST)

vjmap 的瓦片出图有两套机制:

  1. 按地图内置默认 layer 出图
    open_map 之后,可直接用 raster_tile_url() / vector_tile_url() 拿到 {z}/{x}/{y} 占位符 URL,喂给 vjmap。
  2. 按自定义 style 出图
    create_map_style() 拿到 stylename,把它作为 layer 段传给 tile URL,即可得到"按该 style 渲染"的瓦片。
    后续可用 cmd_update_style() 原地修改;用 cmd_switch_layers() 临时切换可见图层;用 slice_layer() 触发预切片缓存;用 get_slice_cache_zoom() 查询当前已缓存到几级;用 delete_style() 清理。

WMS 出图走的是独立管线,用 wms_tile_url() 构造即可,单图走 cmd/wms/{mapid}/{version},多图叠加走 /service/wms


# 快速索引

序号 SDK 方法 HTTP URL
16 raster_tile_url / rasterTileUrl GET /api/v1/map/tile/{mapid}/{version}/{layer}/{z}/{x}/{y}
17 vector_tile_url / vectorTileUrl GET /api/v1/map/tile/{mapid}/{version}/{layer}/{z}/{x}/{y}.mvt
18 wms_tile_url / wmsTileUrl(单图) GET /api/v1/map/cmd/wms/{mapid}/{version}
19 wms_tile_url / wmsTileUrl(多图) GET /api/v1/service/wms
20 create_map_style / createMapStyle POST /api/v1/map/cmd/createMapStyle/{mapid}/{version}
21 cmd_switch_layers / cmdSwitchLayers POST /api/v1/map/cmd/switchlayer/{mapid}/{version}
22 cmd_update_style / cmdUpdateStyle POST /api/v1/map/cmd/updateStyle/{mapid}/{version}
23 slice_layer / sliceLayer POST /api/v1/map/cmd/slicelayer/{mapid}/{version}
24 get_slice_cache_zoom / getSliceCacheZoom POST /api/v1/map/cmd/getSliceCacheZoom/{mapid}/{version}
25 delete_style / deleteStyle POST /api/v1/map/cmd/deletestyle/{mapid}/{version}

注意:10/21/22/23/24/25 这 6 个是 cmd 路由,POST 必须带非空 JSON body(即使是 {"noop":true}),否则会返回 500 param error。SDK 已经替你处理好这点,直接调方法即可。


# 16 · 栅格瓦片 URL

SDKsvc.raster_tile_url(mapid?, version?, layer?, fileid?)

# 请求参数

字段 类型 说明
mapid string 可省,默认为当前 _cur_map.mapid
version string 可省,默认 v1
layer string 可省,默认 _(即图档内置默认 style)。若先调用 create_map_style,把返回的 stylename 传进来即可
fileid string 可省。主要用于 tile 缓存破坏。后端不校验,只要 {mapid,version,layer} 对,tile 就出得来

# 实采示例

"http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/_/{z}/{x}/{y}?tag=c47be46a2ad2&token=<jwt>"
1

# Python

from vjmap import Vjmap, MapOpenWay

svc = Vjmap("http://127.0.0.1:27660/api/v1", TOKEN)
svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender)
print(svc.raster_tile_url())
1
2
3
4
5

# Node.js

import { Vjmap, MapOpenWay } from './sdk/nodejs/vjmap.js';

const svc = new Vjmap('http://127.0.0.1:27660/api/v1', TOKEN);
await svc.openMap({ mapid: 'sys_zp', version: 'v1', mapopenway: MapOpenWay.GeomRender });
console.log(svc.rasterTileUrl());
1
2
3
4
5

# 17 · 矢量瓦片 URL(MVT)

SDKsvc.vector_tile_url(...),参数与 raster_tile_url 完全一致,区别是 URL 末尾多了 .mvt 后缀。

# 实采示例

"http://127.0.0.1:27660/api/v1/map/tile/sys_zp/v1/main/{z}/{x}/{y}.mvt?tag=c47be46a2ad2&token=<jwt>"
1

{layer} 默认是 main(内置 MVT 默认名),不是 _

# 用法

MVT 非常适合作为 vjmap vector source:

map.addSource('vj', {
  type: 'vector',
  tiles: [svc.vectorTileUrl()],
  minzoom: 0,
  maxzoom: 22,
});
map.addLayer({ id: 'vj-line', source: 'vj', 'source-layer': 'default', type: 'line' });
1
2
3
4
5
6
7

# 18 / 19 · WMS 瓦片 URL

SDKsvc.wms_tile_url(mapid, version?, *, layer?, width?, height?, bbox?, srs?, backcolor?, mvt?)

# 参数规则

  • mapid: 可以是字符串,也可以是 list[str]
    • 单图 → 走 /api/v1/map/cmd/wms/{mapid}/{version}
    • 多图 → 走 /api/v1/service/wms?mapid=a;b;c&version=v1;v1;v1
  • bbox: 默认 {bbox-epsg-3857} 占位符。如果你直接用 如Leaflet,就保留占位符交给前端去替换。SDK 刻意不对 bbox 做 percent-encode
  • srs: 坐标系,默认 EPSG:3857
  • mvt=True: URL 末尾加 .mvt,输出矢量。
  • backcolor: 背景色,十进制 RGB(0 = 黑/透明)。
  • layer: 可选,指定某个 stylename 渲染。

# 实采示例

单图:

"http://127.0.0.1:27660/api/v1/map/cmd/wms/sys_zp/v1?bbox={bbox-epsg-3857}&srs=EPSG%3A3857&width=512&height=512&token=<jwt>"
1

多图:

"http://127.0.0.1:27660/api/v1/service/wms?bbox={bbox-epsg-3857}&mapid=sys_zp&version=v1&srs=EPSG%3A3857&width=256&height=256&token=<jwt>"
1

# Python

url = svc.wms_tile_url("sys_zp", "v1", width=512, height=512)
url_multi = svc.wms_tile_url(["sys_zp", "other"], ["v1", "v1"])
1
2

# Node.js

const url = svc.wmsTileUrl({ mapid: 'sys_zp', version: 'v1', width: 512, height: 512 });
const urlMulti = svc.wmsTileUrl({ mapid: ['sys_zp', 'other'], version: ['v1', 'v1'] });
1
2

# 20 · 创建地图样式 createMapStyle

URLPOST /api/v1/map/cmd/createMapStyle/{mapid}/{version}
SDKsvc.create_map_style(mapid, version, *, layeron, layeroff, backcolor, clipbounds, expression, lineweight, is_geom_layer=True, name)

# 请求参数

字段 类型 说明
geom bool true 几何渲染(默认);false 栅格渲染
name string style 名字,可自动生成
layeron string / 数组 可见图层;数组传入时 SDK 会自动转成 (0,1,2) 格式
layeroff string / 数组 隐藏图层
backcolor int 背景色(0=黑色,常用于暗色底图)
clipbounds string 裁剪范围 JSON 字符串
expression string Cesium 风格的颜色表达式
lineweight int[] 线宽缩放 [默认,最小,最大]

# 响应(实采)

{
  "stylename": "sfbd35705c"
}
1
2
3

返回的 stylename 就是后续作为瓦片 URL 中 {layer} 段的值。

# Python

style = svc.create_map_style("sys_zp", "v1", layeron="*", backcolor=0)
tile_url = svc.raster_tile_url(layer=style["stylename"])
1
2

# Node.js

const style = await svc.createMapStyle({ mapid: 'sys_zp', version: 'v1', layeron: '*', backcolor: 0 });
const tileUrl = svc.rasterTileUrl({ layer: style.stylename });
1
2

# curl

curl -X POST "http://127.0.0.1:27660/api/v1/map/cmd/createMapStyle/sys_zp/v1?token=$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"geom":true,"layeron":"*","backcolor":0}'
1
2
3

# 21 · 切换可见图层 switchlayer

URLPOST /api/v1/map/cmd/switchlayer/{mapid}/{version}
SDKsvc.cmd_switch_layers(visible_layers, *, expression?, dark_mode?, mapid?, version?)

# 请求参数

字段 类型 说明
visibleLayer string[] 要可见的图层 id 列表
expression string 颜色表达式(与 createMapStyle 同)
darkMode bool 是否暗色(可在 openMap 时也指定)

# 响应(实采)

{
  "layerid": "s24d720029"
}
1
2
3

createMapStylestylename 相似,layerid 指向新的内部 style(后端按参数 hash 复用,不会无限增长)。

# Python

res = svc.cmd_switch_layers(["0"])
print(res["layerid"])
1
2

# Node.js

const res = await svc.cmdSwitchLayers(["0"]);
console.log(res.layerid);
1
2

# 22 · 原地修改样式 updateStyle

URLPOST /api/v1/map/cmd/updateStyle/{mapid}/{version}
SDKsvc.cmd_update_style(*, name, layeron, layeroff, backcolor, clipbounds, expression, lineweight, mapid?, version?)

createMapStyle 最大的区别:它可以修改已有 style(传同名 name),而不是每次新建。

# 响应(实采)

{
  "bounds": "[587227463.27, 3103509765.63, 588006538.39, 3104288840.75]",
  "boundsChange": false,
  "layerid": "m765111cb4"
}
1
2
3
4
5

boundsChange = false 表示裁剪范围没变;若 clipbounds 修改了,boundsboundsChange 会反映出来。

# Python

svc.cmd_update_style(
    mapid="sys_zp", version="v1",
    name="nb_style_1",
    layeron="*",
    backcolor=0,
)
1
2
3
4
5
6

# Node.js

await svc.cmdUpdateStyle({
  mapid: 'sys_zp', version: 'v1',
  name: 'nb_style_1',
  layeron: '*',
  backcolor: 0,
});
1
2
3
4
5
6

# 23 · 触发预切片缓存 slicelayer

URLPOST /api/v1/map/cmd/slicelayer/{mapid}/{version}
SDKsvc.slice_layer(*, layer, zoom, ismvt, iscancel, is_all_cancel, batch_num?, idle_batch_sleep_ms?, busy_batch_sleep_ms?, mapid?, version?)

# 请求参数

字段 类型 说明
layer string 要预切的 layer id(即 stylename)
zoom int / int[] / string 要切的 zoom 层级。单个或数组
ismvt bool true 切 MVT,false 切栅格
iscancel bool 取消本次任务
isAllCancel bool 取消该地图所有正在进行的切片任务
batchnum int 批处理大小
idle_batch_sleepms int 空闲批间隔毫秒
busy_batch_sleepms int 忙时批间隔毫秒

# 响应(实采)

取消切片:

{}
1

即空对象。真实触发切片时后端会流式推进度({progress:0.5,total:...}),这里我们的 SDK 只取第一响应。

# Python

svc.slice_layer(layer="s24d720029", zoom=[0, 1, 2, 3], ismvt=False)
1

# Node.js

await svc.sliceLayer({ layer: 's24d720029', zoom: [0,1,2,3], ismvt: false });
1

# 24 · 查询已缓存的切片级别 getSliceCacheZoom

URLPOST /api/v1/map/cmd/getSliceCacheZoom/{mapid}/{version}
SDKsvc.get_slice_cache_zoom(*, layer, ismvt, mapid?, version?)

# 响应(实采)

{
  "sliceCacheZoom": 0
}
1
2
3

sliceCacheZoom = 当前已稳定缓存到的最大 zoom。若 = 0 通常表示还没跑过预切片。

# Python / Node

print(svc.get_slice_cache_zoom(layer="s24d720029", ismvt=False))
1
console.log(await svc.getSliceCacheZoom({ layer: 's24d720029', ismvt: false }));
1

# 25 · 删除自定义样式 deletestyle

URLPOST /api/v1/map/cmd/deletestyle/{mapid}/{version}
SDKsvc.delete_style(stylename, *, mapid?, version?)

# 请求参数

字段 类型 说明
stylename string 要删除的 style 名(即 create_map_style 返回的 stylename

# 响应(实采)

即便 stylename 不存在也返回空对象(幂等):

{}
1

# Python / Node

svc.delete_style("sfbd35705c", mapid="sys_zp", version="v1")
1
await svc.deleteStyle('sfbd35705c', { mapid: 'sys_zp', version: 'v1' });
1

# 组合示例:从开图到看地图

from vjmap import Vjmap, MapOpenWay

svc = Vjmap("http://127.0.0.1:27660/api/v1", TOKEN)
svc.open_map(mapid="sys_zp", version="v1", mapopenway=MapOpenWay.GeomRender)

# 1. 自定义一个黑底白线风格
style = svc.create_map_style(
    "sys_zp", "v1",
    layeron="*",
    backcolor=0,
    expression="gOutColorRed:=255;gOutColorGreen:=255;gOutColorBlue:=255;gOutColorAlpha:=255;",
)

# 2. 拿 tile URL 给 vjmap
raster = svc.raster_tile_url(layer=style["stylename"])

# 3. 预切前 4 级,加快首屏
svc.slice_layer(layer=style["stylename"], zoom=[0,1,2,3], ismvt=False)

# 4. 不用了,清掉
svc.delete_style(style["stylename"], mapid="sys_zp", version="v1")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# api/03-query.md

# 要素查询 / 几何

分类代号: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

URLPOST /api/v1/map/cmd/queryFeatures/{mapid}/{version}
SDKsvc.query_features(querytype, ...) / svc.queryFeatures({ querytype, ... })

# querytype 枚举

含义 必填参数
point 点查询:在屏幕像素范围内找要素 x, y, pixelsize, zoom, pixelToGeoLength
rect 矩形查询:几何 bounds 内找要素 x1, y1, x2, y2
expresion 表达式查询:按 DWG 属性字段过滤 expr
condition 条件查询:按后端预定义条件 condition(可空),可选 bounds

⚠️ expresion 是后端拼写,不是 expression

# 可选参数(所有 querytype 通用)

字段 说明
limit 最大返回条数(同时会写入 maxReturnCountlimit 两个字段)
fields 要返回的属性字段,逗号分隔,空串 = 全部
geom 是否返回几何(默认 true,false 则只返回 envelop/attrs)
simplifyTolerance 几何简化容差
useCache 开启查询缓存(condition/expresion 常用)
toMapCoordinate 是否把返回坐标转成地图坐标系
maxGeomBytesSize 单条几何字节大小上限(超过则降级为 envelop)

# 响应结构

{
  "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
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • recordCount后端实际扫到的全量数量,不是本次返回条数。想分页时根据 recordCount + beginpos 自行控制。
  • geojson 是字符串而非对象,调用方需要再 JSON.parse()
  • isEnvelop=true 说明 geojson 被替换成了 envelop(单条太大或命中 maxGeomBytesSize)。

# 实采(rect 查询 sys_zp/v1,limit=5)

{
  "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"
  }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 实采(condition 查询,limit=1)

{
  "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
  }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

recordCount=6982 表示该地图共 6982 个要素满足条件;limit=1 只取第一条。

# 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,
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Node.js

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,
});
1
2
3
4
5
6
7
8
9
10
11

# curl

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}'
1
2
3

# 27 · 构造实体几何 createEntityGeoData

URLPOST /api/v1/map/cmd/createEntityGeoData/_null/v1
SDKsvc.create_entity_geo_data(mapdata=..., map_extent=..., render_accuracy=..., exclude_attribute=..., use_zip=...)

mapdata 是前端 @vj/draw 构造的 DWG 结构体(JSON 字符串)。独立使用场景较少,通常和前端绘图配合。

# 请求参数

字段 类型 说明
mapdata string 前端传入的 DWG JSON 字符串
mapExtent string 地图范围 JSON:[minx,miny,maxx,maxy]
renderAccuracy number 渲染精度(像素)
excludeAttribute bool 是否排除属性(默认 true)
useZip bool 是否对 mapdata 做 gzip 压缩(默认 true)

# 响应

  • 合法输入:{ "result": [...], "bounds": "...", ... }(与 queryFeatures 类似)

# 28 · 坐标转换 coordtransform

URLPOST /api/v1/map/cmd/coordtransform/_/_
SDKsvc.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 两点)

{
  "points": [
    [12957254.769864663, 4852582.082864688],
    [13522423.824622115, 3662655.1833045874]
  ]
}
1
2
3
4
5
6

# 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]]
1
2
3
4
5
6

# Node.js

const res = await svc.coordTransform({
  srs: 'EPSG:4326',
  crs: 'EPSG:3857',
  points: [[116.397, 39.908], [121.474, 31.23]],
});
console.log(res.points);
1
2
3
4
5
6

# 29 · WKT → PROJ4 prjWktToPrj4Str

URLPOST /api/v1/map/cmd/prjWktToPrj4Str/_/_
SDKsvc.prj_wkt_to_prj4_str(wkt, from_="")

# 请求参数

字段 类型 说明
wkt string ESRI prj 文件内容或 WKT 字符串
from string 数据来源。空串 = WKT;其他可选 wmsauto / xml / urn / crsurl / url / micoordsys / pci

# 响应(实采:WGS84 WKT)

{ "proj4": "+proj=longlat +datum=WGS84 +no_defs" }
1

# Python

wkt = open("sample.prj", encoding="utf-8").read()
res = svc.prj_wkt_to_prj4_str(wkt)
print(res["proj4"])
1
2
3

# Node.js

import { readFileSync } from 'node:fs';
const wkt = readFileSync('sample.prj', 'utf8');
const res = await svc.prjWktToPrj4Str(wkt);
console.log(res.proj4);
1
2
3
4


# api/04-analysis.md

# 图形分析

分类代号:C.4 本章共 6 个接口

这一组接口都是 vjmap 的"增值分析"能力,参数差异大、且对输入的 DWG/图像质量要求很高,因此 SDK 采用 pass-through 风格:你按下面文档组参数,SDK 负责 POST 与错误处理,不做参数字段级强校验

全部走 POST /api/v1/map/cmd/{cmd}/_null/v1_null 表示不绑定具体 mapid,参数里自带)。


# 快速索引

序号 SDK 方法 cmd
30 cmd_map_diff / cmdMapDiff mapDiff
31 cmd_map_compare / cmdMapCompare mapCompare
32 cmd_extract_table / cmdExtractTable extractTable
33 cmd_extract_center_lines / cmdExtractCenterLines extractCenterLines
34 cmd_match_object / cmdMatchObject objectMatch
35 cmd_image_svg_to_dwg / cmdImageSvgToDwg imageSvgToDwg

# 30 · 像素差异 mapDiff

比较两张 DWG 渲染成图片后的像素差异,返回增删改的像素块外包矩形中心点。

# 请求参数

字段 类型 说明
mapid1 / version1 string 旧图
mapid2 / version2 string 新图
darkMode bool 是否使用暗色底渲染
bufferSize int 像素容差,相同像素在此距离内不判为"改"
mergeClose bool 合并靠得很近的差异块

# 响应(实采:同图 vs 同图)

{
  "del": "",
  "modify": "",
  "new": "",
  "status": true
}
1
2
3
4
5
6

有差异时 del / modify / newx1,y1,x2,y2;x3,y3,x4,y4 格式的字符串;svc.cmd_map_diff() 不做 parse,调用方按需切分。

# Python

res = svc.cmd_map_diff(
    mapid1="sys_zp", version1="v1",
    mapid2="sys_zp", version2="v2",
    bufferSize=5,
)
1
2
3
4
5

# 31 · 实体对比 mapCompare

mapDiff 不同:mapCompare 比较的是 DWG 实体,不是渲染像素。

# 请求参数

字段 类型 说明
mapid1 / version1, mapid2 / version2 string 两张图
tolerancePos number 坐标容差
toleranceAngle number 角度容差(度)

后端会先做一次快速 hash 去重。真实对比时返回:

{
  "status": true,
  "add": [...],       // 仅在新图存在的实体 id
  "del": [...],       // 仅在旧图存在
  "modify": [...]     // 两边都有但有差异
}
1
2
3
4
5
6

# Python

svc.cmd_map_compare(mapid1="a", version1="v1", mapid2="a", version2="v2")
1

# 32 · 表格提取 extractTable

自动从 DWG 里识别"线条围出来的表格",返回单元格网格与每格文本。最常用于检图、清单导出。

# 请求参数

字段 说明
mapid / version 要识别的图
layer 可选,只在某 style / layer 上跑
bbox 可选,限定在地图坐标系下的 bbox
minCellWidth / minCellHeight 最小单元格尺寸阈值

# 响应(实采:sys_zp/v1,无额外参数)

{
  "tables": [{
    "attr": {
      "cellEmptyRatio": 45,
      "tableCellMaxCount": 70,
      "tableTextCount": 39,
      "unLinkLineRatio": 0
    },
    "colCount": 4,
    "cols": ["587693680.21", "587698180.22", "587717259.64", "..."],
    "datas": [["类目", "数量", "价格", "说明"], [...]],
    "rows": ["...", "..."]
  }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • cols / rows:单元格网格线的坐标;
  • datas[i][j]:每格文本(可能含编码乱码,视 DWG 字体而定)。

# Python

res = svc.cmd_extract_table(mapid="sys_zp", version="v1")
for t in res.get("tables", []):
    print(t["colCount"], "x", len(t["rows"]))
1
2
3

# Node.js

const res = await svc.cmdExtractTable({ mapid: 'sys_zp', version: 'v1' });
console.log(res.tables?.length);
1
2

# 33 · 中心线提取 extractCenterLines

对闭合多边形(如道路、管线)抽取中心线。

# 请求参数

字段 说明
mapid / version 源图
layer 可选,限定 layer
expr 可选,按属性过滤实体(与 queryFeatures 的 expr 同义)

# 响应(实采:sys_zp/v1)

{
  "centerlines": [
    [[587562497.79, 3103857885.77], [587556239.69, 3103860401.38]],
    [[587603886.22, 3103921932.46], [587602627.19, 3103925333.77]]
  ]
}
1
2
3
4
5
6

centerlines 是"多段折线数组";每条线由若干 [x, y] 顶点构成。

# Python

res = svc.cmd_extract_center_lines(mapid="sys_zp", version="v1")
for line in res["centerlines"]:
    print("line with", len(line), "verts")
1
2
3

# 34 · 目标匹配 objectMatch

在底图中找与模板图形相似的子图(样式 + 尺寸)。常用于批量识别图章、标注块、设备。

# 请求参数

字段 类型 默认值 说明
mapid / version string 底图
templateMapid / templateVersion string 模板图
objectBounds string / number[] 必填:模板所在的 bbox(地图坐标) [x1,y1,x2,y2]
score number 0.6 相似度阈值
canOverlap bool false 命中框是否允许重叠
maxOverlap number 0.3 允许的最大重叠比例
toleranceAngle number 180 角度容差(度),180 = 任意旋转
minReduceArea number 256 小于此面积的命中框被忽略

# 响应

成功时返回:

{
  "status": true,
  "matches": [
    { "bounds":"[x1,y1,x2,y2]", "angle": 0.0, "score": 0.92 },
    ...
  ]
}
1
2
3
4
5
6
7

# Python

res = svc.cmd_match_object(
    mapid="sys_zp", version="v1",
    templateMapid="sys_zp", templateVersion="v1",
    objectBounds="[587661000,3103886000,587661200,3103886200]",
    score=0.7, toleranceAngle=90,
)
1
2
3
4
5
6

# 35 · SVG / 图像转 DWG imageSvgToDwg

把 SVG 字符串或位图转换成 DWG 实体。对位图会先做线条检测 + 矢量化。

# 请求参数

字段 说明
svgData SVG 文本
imageUrl 远程图片 URL(与 svgData 二选一)
threshold 位图二值化阈值(0–255)
mapid / version 可选:把结果直接写入该地图;为空则只返回 DWG JSON

# 响应(实采:svgData="<svg></svg>",空内容)

{
  "error": "image or svg content is empty",
  "status": false
}
1
2
3
4

合法输入时返回:

{
  "status": true,
  "mapdata": "<DWG JSON 字符串>"
}
1
2
3
4

可以把 mapdata 传给 create_entity_geo_data(见 03-query.md)渲染。


# 通用注意

  1. 这组接口都可能 耗时 >10s,SDK 默认 timeout=60s,建议在业务层再做重试与超时兜底。
  2. 后端遇到参数非法时常返回 {"error": "...", "status": false}——不是 HTTP 4xx/5xx。SDK 会把它当成 2xx 正常响应返回给调用方。
  3. 返回 "status": falseerror 字段的语言依系统 locale 而定(我们这里抓到了中/英混合的错误串)。


# api/05-compose-export.md

# 组合 / 拆分 / 导出

分类代号:C.5 本章共 6 个接口

把多张 DWG 组合成一张、把一张大图按图框 / 图层拆成多张、把图纸导出为 PDF / DXF 或离线瓦片包——这一章就是"图纸经纪人"。

共同特点:这 6 个接口都相对重,后端会真实创建新图档 / 新文件。测试环境请用临时 mapid,不要对生产图做实验。


# 快速索引

序号 SDK 方法 URL
36 cmd_compose_new_map / cmdComposeNewMap POST /api/v1/map/cmd/composeNewMap/_null/v1
37 cmd_split_map / cmdSplitMap POST /api/v1/map/cmd/cmdSplitMap/_null/v1
38 cmd_export_layout / cmdExportLayout POST /api/v1/map/cmd/exportLayout/_null/v1
39 cmd_export_pdf / cmdExportPdf POST /api/v1/map/cmd/exportPdf/{mapid}/{version}
40 cmd_export_dxf / cmdExportDxf POST /api/v1/map/cmd/exportDxf/{mapid}/{version}
41 cmd_save_offline_tile_db / cmdSaveOfflineTileDb POST /api/v1/map/cmd/saveOfflineTileDb/_null/v1

# 36 · 组合新地图 composeNewMap

把多张已有 DWG 按几何变换(偏移 + 旋转)拼成一张。

# 请求参数

字段 类型 说明
maps array 组合清单:[{mapid, version, dx?, dy?, angle?}]
newMapid string 新图 mapid
newVersion string 新图 version(可省)
bounds string 可选:组合后的 bbox 限定

# 响应

成功:{ "status": true, "mapid": "...", "fileid": "...", "version": "v1" }
失败(实采:源文件不存在):

{
  "error": "file is not exist, ",
  "status": false
}
1
2
3
4

# Python

svc.cmd_compose_new_map(
    maps=[
        {"mapid": "part_a", "version": "v1", "dx": 0,      "dy": 0},
        {"mapid": "part_b", "version": "v1", "dx": 1000.0, "dy": 0, "angle": 0},
    ],
    newMapid="assembled_map",
)
1
2
3
4
5
6
7

# Node.js

await svc.cmdComposeNewMap({
  maps: [
    { mapid: 'part_a', version: 'v1', dx: 0, dy: 0 },
    { mapid: 'part_b', version: 'v1', dx: 1000, dy: 0, angle: 0 },
  ],
  newMapid: 'assembled_map',
});
1
2
3
4
5
6
7

# 37 · 拆分子图 cmdSplitMap

按图框线 / 按图层把一张大图拆成多张小图。

# 请求参数

字段 类型 说明
mapid / version string 源图
splitType string frame(按图框)/ layer(按图层)
framelayer string 仅 splitType=frame 时:图框线所在图层
clipBounds string 也可以直接给一系列 bbox 列表拆
outNamePrefix string 新图 mapid 前缀,默认用源 mapid + 序号

# 响应

成功:{ "status": true, "maps": [{mapid, version, bounds}, ...] }
失败(实采:frame 拆分未给 clipBounds):

{ "error": "clipBounds is empty" }
1

# 38 · 导出布局为 DWG exportLayout

从 DWG 的布局空间(paper space)单独导出一张新 DWG。CAD 里经常一个文件多个布局(A3、A2、首页),这个接口能一次性把某个布局变成独立图档。

# 请求参数

字段 类型 说明
mapid / version string 源图
layoutIndex int 布局序号(0 起)
layoutName string 或直接用布局名,优先级高于 layoutIndex
newMapid string 可选:直接导入为新地图
newVersion string 可选:新地图 version

# 响应(实采:sys_zp/v1, layoutIndex=0)

{
  "fileid": "pf68817b9655",
  "status": true
}
1
2
3
4

fileid 指向后端缓存里的新 DWG 文件,可以接着调用 open_map(mapid=...) 打开,或用 exportDxf / exportPdf 进一步出图。

# Python

res = svc.cmd_export_layout(mapid="sys_zp", version="v1", layoutIndex=0)
assert res["status"]
print(res["fileid"])  # 'pf68817b9655'
1
2
3

# 39 · 导出 PDF exportPdf

把指定图纸输出为 PDF。支持按地图 bbox 截图 + 指定纸张。

# 请求参数

字段 类型 说明
bounds / bbox string 要导出的 bbox:[minx,miny,maxx,maxy]
width / height number 纸张尺寸(mm),常用 A4=297×210
scale number 比例尺(可选)
darkMode bool 是否暗色渲染
outName string 自定义输出文件名
layer string 仅导出某 style / layer

# 响应(实采:sys_zp/v1, 297×210 mm)

{
  "path": "download/d5f939154c.pdf",
  "status": true
}
1
2
3
4

完整下载 URL 是 http://127.0.0.1:27660/{path}?token=<jwt>

# Python

res = svc.cmd_export_pdf(
    mapid="sys_zp", version="v1",
    width=297, height=210,
)
url = svc.base_gateway() + res["path"] + f"?token={svc.token}"
1
2
3
4
5

# Node.js

const res = await svc.cmdExportPdf({
  mapid: 'sys_zp', version: 'v1',
  width: 297, height: 210,
});
const url = `${svc.baseGateway()}${res.path}?token=${svc.token}`;
1
2
3
4
5

# 40 · 导出 DXF exportDxf

把当前图档(或其子集)导出为 ASCII DXF 文件。

# 请求参数

字段 类型 说明
bounds / bbox string 裁剪 bbox
layer string 限定 style / layer
dxfVersion string DXF 版本 tag:AC1009 (R12), AC1018 (R2004) 等
outName string 自定义输出文件名

# 响应

成功:{ "status": true, "path": "download/xxx.dxf" }
失败(实采:当前地图没有可导出图元):

{
  "error": "Invalid Symbol Table name",
  "status": false
}
1
2
3
4

# Python

res = svc.cmd_export_dxf(
    mapid="sys_zp", version="v1",
    dxfVersion="AC1018",
    bounds="[587661158,3103885970,587661256,3103886087]",
)
1
2
3
4
5

# 41 · 离线瓦片包 saveOfflineTileDb

把指定地图范围预切并打成 .mbtiles / .sqlitedb 离线包,方便前端离线展示。

# 请求参数

字段 类型 说明
mapid / version string 源图
minzoom / maxzoom int zoom 范围
bounds string 要切的 bbox
ismvt bool true 切 MVT(.mbtiles),false 切栅格 PNG
outName string 输出文件名

# 响应

成功:{ "status": true, "path": "download/xxx.mbtiles" }
失败(实采:缺 bounds):

{ "error": "param error" }
1

# Python

res = svc.cmd_save_offline_tile_db(
    mapid="sys_zp", version="v1",
    minzoom=0, maxzoom=6,
    bounds="[587661158,3103885970,587661256,3103886087]",
    ismvt=True,
    outName="sys_zp_offline.mbtiles",
)
1
2
3
4
5
6
7

# 组合示例:DWG → PDF 一把梭

# 1. 上传并打开 DWG
up = svc.upload_map("project.dwg", mapid="proj", version="v1")
svc.open_map(
    mapid="proj", version="v1",
    fileid=up["fileid"], uploadname=up["uploadname"],
    mapopenway=vjmap.MapOpenWay.GeomRender,
)

# 2. 导出第 0 号布局
lay = svc.cmd_export_layout(mapid="proj", version="v1", layoutIndex=0,
                             newMapid="proj_layout0", newVersion="v1")

# 3. 打开刚生成的布局图并导 PDF
svc.open_map(mapid="proj_layout0", version="v1",
             fileid=lay["fileid"], uploadname="proj_layout0.dwg",
             mapopenway=vjmap.MapOpenWay.GeomRender)
pdf = svc.cmd_export_pdf(mapid="proj_layout0", version="v1",
                          width=297, height=210)
print(svc.base_gateway() + pdf["path"])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


# api/06-webcad.md

# WebCAD(条件启用)

分类代号:C.6 本章共 6 个接口


# 启用判定

最推荐的方式:

svc = vjmap.Vjmap(API, TOKEN)
if svc.webcad_enabled():
    draws = svc.list_webcad_draws()
else:
    print("本服务未启用 WebCAD")
1
2
3
4
5

svc.webcad_enabled() 的判定规则:

  1. GET /api/v1/service/webcad 若返回 2xx 且 没有 enabled=false / error 字段,视为"可用";
  2. 若返回 4xx/5xx 或业务错误(如 Token is invalid),视为"不可用"。

# 本仓库实采

// /api/v1/service/webcad?token=...
{
  "status": 403,
  "body": {
    "code": 7,
    "data": { "reload": true },
    "msg": "Token is invalid"
  }
}
1
2
3
4
5
6
7
8
9

# 快速索引

序号 SDK 方法 URL
42 list_webcad_draws / listWebcadDraws GET /api/v1/map/cmd/listWebcadDraws/_/v1
43 get_webcad_data / getWebcadData POST /api/v1/map/cmd/getWebcadData/{mapid}/{version}
44 save_webcad_patch / saveWebcadPatch POST /api/v1/map/cmd/saveWebcadPatch/{mapid}/{version}
45 delete_webcad_draw / deleteWebcadDraw POST /api/v1/map/cmd/deleteWebcadDraw/{mapid}/{version}
46 convert_webcad / convertWebcad POST /api/v1/map/cmd/convertWebCAD/{mapid}/{version}
47 export_webcad / exportWebcad POST /api/v1/map/cmd/exportWebCAD/_null/v1

# 42 · 列出 WebCAD 图 listWebcadDraws

URLGET /api/v1/map/cmd/listWebcadDraws/_/v1
响应(启用时):

{
  "draws": [
    { "mapid": "proj_wc", "version": "v1", "updatedAt": "2024-..." },
    ...
  ]
}
1
2
3
4
5
6

# Python / Node

svc.list_webcad_draws()
1
await svc.listWebcadDraws();
1

# 43 · 读取 WebCAD 数据 getWebcadData

URLPOST /api/v1/map/cmd/getWebcadData/{mapid}/{version}

# 请求参数

字段 说明
branch 分支名
includePatches 是否把历次补丁一并返回

# 响应(启用时)

{
  "mapdata": "<DWG JSON 字符串>",
  "branch": "main",
  "headVersion": 12,
  "patches": []
}
1
2
3
4
5
6

# Python

svc.get_webcad_data("proj_wc", "v1", branch="main")
1

# 44 · 保存 WebCAD 补丁 saveWebcadPatch

URLPOST /api/v1/map/cmd/saveWebcadPatch/{mapid}/{version}

# 请求参数

字段 说明
patch 一段 JSON/字符串形式的差异描述
branch 分支名
author 作者标识(可选)
comment 提交说明
baseVersion 基于哪个补丁号提交,用于冲突检测

# 响应(启用时)

{ "status": true, "version": 13 }
1

# Python

svc.save_webcad_patch(
    "proj_wc", "v1",
    patch='[{"op":"add","entity":{...}}]',
    branch="main",
    author="alice",
    comment="add hatch",
    baseVersion=12,
)
1
2
3
4
5
6
7
8

# 45 · 删除 WebCAD 图 deleteWebcadDraw

URLPOST /api/v1/map/cmd/deleteWebcadDraw/{mapid}/{version}

# 请求参数

字段 说明
branch 只删某个分支;为空则删整图

# 响应(启用时)

{ "status": true }
1

# 46 · 转为 WebCAD convertWebCAD

把一张已上传的 DWG 转成 WebCAD 可用的结构(初始化分支、预计算可编辑实体)。

URLPOST /api/v1/map/cmd/convertWebCAD/{mapid}/{version}

# 响应(启用时)

{
  "status": true,
  "branch": "main",
  "entityCount": 12345
}
1
2
3
4
5

# 47 · 导出 WebCAD 为 DWG exportWebCAD

URLPOST /api/v1/map/cmd/exportWebCAD/_null/v1

# 请求参数

字段 说明
mapid / version 要导出的 WebCAD 图
branch 导出哪个分支(默认 main)
newMapid / newVersion 新 DWG 的 mapid / version

# 响应(启用时)

{ "status": true, "path": "download/xxx.dwg" }
1

# api/07-workspace.md

# 工作区

分类代号:C.7 本章共 5 个接口

如果还没读过,建议先看 入门 · 工作区,了解 vjmap 的命名空间模型。

工作区用来把地图 / KV / 全文搜索按"项目"隔开。默认所有地图都在"匿名工作区";创建命名工作区后,URL 前缀由 /api/v1/map/... 变成 /api/v1/workspace/{name}/...


# 快速索引

序号 SDK 方法 URL
48 get_workspaces / getWorkspaces GET /api/v1/service/getWorkspace
49 workspace_create / workspaceCreate POST /api/v1/service/workspaceCreate
50 workspace_modify / workspaceModify POST /api/v1/service/workspaceModify
51 workspace_delete / workspaceDelete DELETE /api/v1/service/workspaceDelete/{name}
52 workspace_status / workspaceStatus GET /api/v1/service/workspaceStatus

# 48 · 列出工作区 getWorkspace

URLGET /api/v1/service/getWorkspace

  • 非 root 账户仅看到 自己创建的 + isPublic=true 的工作区
  • root(AuthorityId:root)账户看全部

# 响应(实采:创建 1 个后)

[
  {
    "name": "nb_ws_1776831982",
    "alias": "nb 临时测试",
    "isPublic": false,
    "workDir": "nb_ws_1776831982",
    "idleCloseTime": 0,
    "disableFullSearch": false,
    "createTime": "2026-04-22 12:26:22"
  }
]
1
2
3
4
5
6
7
8
9
10
11

一个工作区都没有时返回 null(而不是空数组 [])。

# Python

ws_list = svc.get_workspaces() or []
for w in ws_list:
    print(w["name"], w["alias"])
1
2
3

# Node.js

const ws = (await svc.getWorkspaces()) || [];
for (const w of ws) console.log(w.name, w.alias);
1
2

# 49 · 创建工作区 workspaceCreate

URLPOST /api/v1/service/workspaceCreate

# 请求 body

字段 类型 说明
name string 唯一工作区名(会出现在 URL 路径里,注意字符集)
alias string 显示名,任意字符
workDir string 工作区数据目录名;空串表示与 name 一致
isPublic bool 是否对所有登录用户可见
disableFullSearch bool 是否禁用该工作区的全文搜索索引

# 响应(实采)

{
  "data": "nb_ws_1776831982",
  "status": "ok"
}
1
2
3
4

# Python

svc.workspace_create(
    name="myproj",
    alias="我的项目",
    is_public=False,
)
1
2
3
4
5

# Node.js

await svc.workspaceCreate({ name: 'myproj', alias: '我的项目' });
1

# 50 · 修改工作区 workspaceModify

URLPOST /api/v1/service/workspaceModify

body 必须是 getWorkspaces() 返回的完整 obj(包含 createTime 在内所有字段)。常见用法:先拿再改再 POST。

# 响应(实采)

{
  "name": "nb_ws_1776831982",
  "status": "success"
}
1
2
3
4

# Python

ws_list = svc.get_workspaces() or []
me = next(w for w in ws_list if w["name"] == "myproj")
me["alias"] = "新别名"
me["disableFullSearch"] = True
svc.workspace_modify(me)
1
2
3
4
5

# Node.js

const ws = (await svc.getWorkspaces()) || [];
const me = ws.find(w => w.name === 'myproj');
me.alias = '新别名';
await svc.workspaceModify(me);
1
2
3
4

# 51 · 删除工作区 workspaceDelete

URLDELETE /api/v1/service/workspaceDelete/{name}

# 响应(实采)

{
  "name": "nb_ws_1776831982",
  "status": "success"
}
1
2
3
4

⚠️ 删除不会自动清理工作区内的地图数据;如需彻底清除,先在该工作区内删完地图再删工作区。SDK 切换到目标工作区调 delete_map / delete_map_file 即可(见 01-map-management)。

# Python

svc.workspace_delete("myproj")
1

# Node.js

await svc.workspaceDelete('myproj');
1

# 52 · 工作区状态 workspaceStatus

URLGET /api/v1/service/workspaceStatus

查询当前正在服务中的工作区(已启用 fork 进程 / 加载了地图)。若无工作区处于活跃状态,返回 null

# 响应(实采:无活跃工作区)

null
1

活跃时形如:

[
  { "name": "myproj", "status": "running", "pid": 12345, "memory": 128_000_000 }
]
1
2
3

# 在 SDK 中切换工作区

SDK 构造后,可以用 svc.use_workspace("myproj") 把后续所有 map/... / cmd/... 调用都前缀到 /workspace/myproj/

svc = vjmap.Vjmap(API, TOKEN)
svc.use_workspace("myproj")
svc.list_maps()            # -> /api/v1/workspace/myproj/cmd/listmaps/...
svc.use_workspace(None)    # 切回匿名
1
2
3
4

Node.js 同名方法 svc.useWorkspace("myproj")



# api/08-data-storage.md

# 键值数据存储 (KV)

分类代号:C.8 本章共 5 个接口

vjmap 内置了一个按工作区隔离的 KV 数据表(SQLite),用来存用户配置、缩略图、缓存数据等零碎东西。"零碎"并不代表"不好用"——二开里非常常用,比如存每个用户的图层可见性、自定义地图标注等。

存储位置:后端 data/customData.db
自动前缀:所有用户可见 key 都加了隐形 data_ 前缀,本 SDK 已屏蔽此细节。
TTL:可选过期时间(秒),到期自动清理。
批量 key 分隔符:路径里 ;;(双分号)分隔多个 key。


# 快速索引

序号 SDK 方法 URL
53 get_custom_data / getCustomData GET /api/v1/service/data/{key}
54 save_custom_data / saveCustomData POST /api/v1/service/data/{key}
55 delete_custom_data / deleteCustomData DELETE /api/v1/service/data/{key}
56 save_multiple_custom_data / saveMultipleCustomData POST /api/v1/service/datas
57 get_custom_data_keys / getCustomDataKeys GET /api/v1/service/datakeys?prefix=

# 53 · 获取值 data/{key} GET

# 查询参数

字段 取值 说明
retDataType "" / "value" / "prop" 默认返回 {data, prop}value 只要 dataprop 只要 prop
contentType "" / "image" image 时后端把 base64 dataURL 解出,直接返回 PNG 二进制(不是 JSON)

批量:key 路径段用 ;; 拼接多 key,SDK 传字符串列表即可。

# 响应

  • 存在:{ "status": "ok", "data": <value>, "prop": <prop> }
  • 不存在:{ "status": "keyNoExist" }不是 404
  • 批量:{ "values": [...], "props": [...], "status": [...] }

# 实采

// get_custom_data.json
{
  "data": "hello-vjmap",
  "prop": "text/plain",
  "status": "ok"
}
1
2
3
4
5
6
// get_missing.json —— 一定要判断 status
{ "status": "keyNoExist" }
1
2

# Python

d = svc.get_custom_data("myproj/theme")
if d["status"] == "keyNoExist":
    ...
else:
    theme = d["data"]
1
2
3
4
5

# Node.js

const d = await svc.getCustomData('myproj/theme');
if (d.status === 'keyNoExist') { /* ... */ }
1
2

# 批量读

svc.get_custom_data(["a", "b"])
# { "values": ["va","vb"], "props":["pa","pb"], "status":["ok","ok"] }
1
2

# 54 · 保存值 data/{key} POST

# 查询参数

字段 说明
ttl 秒;到点清除。省略或 0 = 长期

# Body 两种写法

  1. JSON{"value": "...", "prop": "..."}prop 可存元数据(MIME、扩展字段)
  2. Raw:直接把文件二进制 POST 上去,作为 value 字段存(SDK 里叫 raw_body / rawBody

# 响应(实采)

{
  "data": "nb_kv_a_1776832185",
  "status": "ok"
}
1
2
3
4

# Python

svc.save_custom_data("myproj/theme",
                    value='{"mode":"dark"}',
                    prop="application/json")

# 带 TTL
svc.save_custom_data("tmp_token",
                    value="eyJ...", ttl=3600)

# Raw bytes
svc.save_custom_data("user_avatar_42",
                    raw_body=open("avatar.png","rb").read())
1
2
3
4
5
6
7
8
9
10
11

# Node.js

await svc.saveCustomData('myproj/theme', {
  value: JSON.stringify({ mode: 'dark' }),
  prop: 'application/json',
});

await svc.saveCustomData('tmp_token', { value: tokenStr, ttl: 3600 });
1
2
3
4
5
6

# 55 · 删除值 data/{key} DELETE

# 查询参数

字段 说明
isPrefix "true" 时把 key 当前缀,批量清除;否则只删单条

# 响应

  • 单条(实采):{"data":"nb_kv_a_...","status":"ok"}
  • 前缀(实采):{"data":"nb_prefix_...","isPrefix":true,"status":"ok"}

# Python

svc.delete_custom_data("myproj/theme")

# 前缀批量
svc.delete_custom_data("myproj/user42/", is_prefix=True)

# 列表批量(走 ;; 分隔)
svc.delete_custom_data(["a", "b", "c"])
1
2
3
4
5
6
7

# 56 · 批量存值 datas POST

URLPOST /api/v1/service/datas

# Body

[{"key":..,"value":..,"prop":..,"ttl":"60"}],注意 ttl字符串秒数。

# 响应(实采)

{ "status": ["ok"] }
1

# Python

svc.save_multiple_custom_data([
    {"key": "cfg/a", "value": "1"},
    {"key": "cfg/b", "value": "2", "ttl": "3600"},
])
1
2
3
4

# Node.js

await svc.saveMultipleCustomData([
  { key: 'cfg/a', value: '1' },
  { key: 'cfg/b', value: '2', ttl: '3600' },
]);
1
2
3
4

# 57 · 按前缀列键 datakeys GET

# 查询参数

字段 说明
prefix 前缀(自动加 data_
retDataType value / prop / valueprop —— 同时取出值,省一次往返
isSys true 时返回带 data_ 内部前缀的系统键(仅 root 场景)

# 响应

  • 仅 keys(实采):
{ "keys": ["nb_kv_a_1776832185", "nb_kv_b_1776832185"] }
1
  • 带值(retDataType=valueprop,实采):
{
  "keys":   ["nb_kv_a_1776832185", "nb_kv_b_1776832185"],
  "props":  ["text/plain",          "bp-1"],
  "values": ["hello-vjmap",         "batch-1"],
  "status": ["ok",                  "ok"]
}
1
2
3
4
5
6

# Python

keys = svc.get_custom_data_keys(prefix="myproj/")["keys"]

# 同时取值
bundle = svc.get_custom_data_keys(prefix="myproj/", ret_data_type="valueprop")
for k, v, p in zip(bundle["keys"], bundle["values"], bundle["props"]):
    print(k, v, p)
1
2
3
4
5
6

# 最佳实践

  1. 前缀分层:用 //: 建立命名空间,如 myproj/user42/tool_state。这样前缀批量删除、列出都方便。
  2. 大文件不要存 KV:value 单条一般 < 1 MB;大图/DWG 用 /map/uploadmap 或独立对象存储。
  3. TTL 清理:用 ttl 让后端帮忙过期;别自己写 crontab。
  4. keyNoExist ≠ 错误:200 正常响应里 status == "keyNoExist" 表示没值,而不是失败。


# api/09-fullsearch.md

# 全文搜索 (Full-text Search)

分类代号:C.9 本章共 3 个接口
可关闭特性config.yamlfullsearch.disable: true 会让这组路由完全不注册;当 full_search 返回 VjmapError(404) 时即判定为"关闭"。

vjmap 内置 bleve (opens new window) 作为 CAD 图纸全文索引,主要用于搜"图上某段文字在哪儿"。典型使用场景:

  • 用户在前端搜索框里输入"消防栓",后端返回所有匹配到的文字 + bbox,前端画框高亮
  • 按图元类型(单行/多行/属性注记/图层名/块名…)过滤
  • 按工作区 / map_ver 隔离

# 快速索引

序号 SDK 方法 URL
58 full_search / fullSearch POST /api/v1/service/fullsearch/search(也支持 GET)
59 full_search_add / fullSearchAdd POST /api/v1/service/fullsearch/add
60 full_search_delete / fullSearchDelete DELETE /api/v1/service/fullsearch/delete

# Document 数据结构

{
  "id":       "unique-id",          // 必填,全库唯一
  "map_ver":  "sys_zp_v1",          // 建议填,便于按图过滤
  "content":  "消防栓",              // 被索引的文本
  "type":     1,                    // 见下方 type 表
  "min_x":    0.0,                  // bbox
  "min_y":    0.0,
  "max_x":    1.0,
  "max_y":    1.0,
  "props":    {...},                // 任意附加属性
  "workspace":"default",            // 为空 = 匿名工作区
  "data":     "..."                  // 原始业务载荷(由你自己定义)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# type 常用值

type 说明
1 单行文字
2 多行文本 MText
3 属性注记 Attribute
4 块属性注记
5 图层名
6 块名
7 线型名
8 填充符号名
9 用户自定义

两种调用方式:

  • GET:所有参数走 query,适合简单关键词
  • POST:所有参数走 JSON body,支持复杂结构(尤其 bounding_box)。

# 请求字段

字段 类型 说明
query string 搜索关键词
map_ver string 多个用 , 分隔;纯 mapid 自动取最新版本
type string 类型过滤:"1" / "1,2,5" / "1-3" / "1-3,6"
workspace string 工作区,多个逗号分隔
limit int 每页条数,默认 10
offset int 分页偏移
fields string 返回字段过滤,逗号分隔
facet bool 是否按 map_ver 聚合 facet
bounding_box string "minx,miny,maxx,maxy""[..]"
time_range string "2023-01-01~2023-12-31""7"(最近 N 天)

# 响应(实采)

{
  "total": 0,
  "hits": [],
  "took": 2
}
1
2
3
4
5

total 是命中数量,hits 是文档数组(含 id / content / score / fragments 等 bleve 字段),took 是耗时毫秒。

# Python

res = svc.full_search(
    query="消防栓",
    map_ver="proj_v1",
    type_="1,2",
    bounding_box="[0,0,1000,1000]",
    limit=20,
)
for hit in res.get("hits", []):
    print(hit["id"], hit.get("content"))
1
2
3
4
5
6
7
8
9

用 GET 走查询串:

svc.full_search(query="消防栓", limit=5, use_get=True)
1

# Node.js

const res = await svc.fullSearch('消防栓', {
  mapVer: 'proj_v1',
  type: '1,2',
  boundingBox: '[0,0,1000,1000]',
  limit: 20,
});
1
2
3
4
5
6

# 59 · 新增文档 fullsearch/add

# Body

{
  "document": { "id":"...","map_ver":"...","content":"..." }
}
1
2
3

# 响应(实采)

{
  "id": "nb_fs_1776832372",
  "message": "Document added successfully"
}
1
2
3
4

# Python

svc.full_search_add({
    "id": "firehydrant_01",
    "map_ver": "proj_v1",
    "content": "消防栓",
    "type": 1,
    "min_x": 100, "min_y": 200, "max_x": 110, "max_y": 210,
})
1
2
3
4
5
6
7

批量导入:本 SDK 只封装了单条 full_search_add。接口 /fullsearch/batchAdd 用同样的 POST JSON({"documents":[...]});本次不做默认封装,可按需自行 svc._request("POST", svc.service_url("fullsearch/batchAdd"), json_body={"documents":[...]})


# 60 · 删除文档 fullsearch/delete

注意是 DELETE 带 JSON body,而不是 ?id=(gin 用 ShouldBindJSON)。

# Body

{ "id": "firehydrant_01" }
1

# 响应(实采)

{
  "id": "nb_fs_1776832372",
  "message": "Document deleted successfully"
}
1
2
3
4

# Python

svc.full_search_delete("firehydrant_01")
1

# 常见错误

HTTP 状态 body 片段 含义
400 "error":"Invalid request parameters" JSON 反序列化失败
400 "error":"Document ID cannot be empty" 未填 id
400 "error":"Invalid bounding box format" bounding_box 解析失败
404 整条路由不存在 fullsearch.disable:true,功能关闭
500 "error":"Failed to initialize service" 后端索引损坏,看服务器日志

# 使用建议

  1. map_ver 隔离:别把所有图纸的文字都塞一个索引里查全局,会拖慢。
  2. 先 bbox 后全文:多数场景只需搜当前视口,给 bounding_box 可大幅缩小候选集。
  3. 批量构建走后台 job:把"从 DWG 抽文字 → 批量 add"放到离线任务里;前端只做搜索。
  4. 关闭索引也没关系:SDK 的 full_search 会在 404 时抛 VjmapError(404),应用层 catch 一下退化到普通查询即可。


# api/10-symbols.md

# 符号库

分类代号:C.10 本章共 3 个接口(只读)

vjmap 自带一个"常用图块/图符"库,按分类组织。二开场景里最常见的用法是:

  • 前端工具条展示"符号面板",用户选一个图符,拖到图上
  • 拖放后前端本地生成图元并调后端 updatemap 持久化
  • 导出 DWG 时把符号实例替换为 DXF BlockRef

本章只覆盖只读三接口(分类列表 / 符号列表 / 符号详情)。写操作(增 / 改 / 删分类与符号)SDK 暂不封装;如果你用 root token,可按 POST /symbol/categoryPUT /symbol/:id 等直接 svc._request(...) 调。


# 快速索引

序号 SDK 方法 URL
61 symbol_categories / symbolCategories GET /api/v1/service/symbol/categories
62 symbol_list / symbolList GET /api/v1/service/symbol/list/{categoryId}
63 symbol_get / symbolGet GET /api/v1/service/symbol/{id}

# 61 · 分类列表 symbol/categories

# 响应(实采)

{
  "data": [
    { "id": "default",     "name": "默认分类",  "createTime": "2026-04-10 11:29:15" },
    { "id": "user_custom", "name": "用户自定义", "createTime": "2026-04-10 11:29:15" }
  ],
  "status": "ok"
}
1
2
3
4
5
6
7

# Python

cats = svc.symbol_categories()["data"]
for c in cats:
    print(c["id"], c["name"])
1
2
3

# Node.js

const cats = (await svc.symbolCategories()).data;
1

# 62 · 符号列表 symbol/list/{categoryId}

按分类分页列出符号。

# 查询参数

字段 默认 说明
page 1 分页起始(1-based)
pageSize 20 每页
keyword - 名称模糊匹配

# 响应(实采:分类为空)

{
  "data": {
    "list": [],
    "total": 0,
    "page": 1,
    "pageSize": 3
  },
  "status": "ok"
}
1
2
3
4
5
6
7
8
9

有数据时 list 元素形如:

{
  "id":         "symbol_abc",
  "categoryId": "default",
  "name":       "消防栓",
  "basePoint":  [0, 0],
  "thumbnail":  "data:image/png;base64,...",
  "data":       "...DWG/SVG 原始数据..."
}
1
2
3
4
5
6
7
8

# Python

page = svc.symbol_list("default", page=1, page_size=20, keyword="栓")
for it in page["data"]["list"]:
    print(it["id"], it["name"])
1
2
3

# Node.js

const page = await svc.symbolList('default', { page: 1, pageSize: 20 });
1

# 63 · 符号详情 symbol/{id}

# 响应

  • 成功:{ "status": "ok", "data": { ...同上"有数据时"结构... } }
  • 未找到:HTTP 404 + { "status": "error", "message": "not found" }

# Python

try:
    sym = svc.symbol_get("symbol_abc")["data"]
except vjmap.VjmapError as e:
    if e.status == 404:
        ...  # 符号不存在
    else:
        raise
1
2
3
4
5
6
7

# 使用范式

下面是一个"前端面板"级别的整合:

def list_all_symbols():
    for cat in svc.symbol_categories()["data"]:
        page = 1
        while True:
            bundle = svc.symbol_list(cat["id"], page=page, page_size=50)["data"]
            for sym in bundle.get("list", []):
                yield cat, sym
            if page * bundle["pageSize"] >= bundle["total"]:
                break
            page += 1
1
2
3
4
5
6
7
8
9
10


# api/11-system.md

# 系统

分类代号:C.11 本章共 4 个接口

这几个接口是运维和监控最常看的:

  • heart:心跳
  • version:版本与构建信息
  • runstatus:当前进程 / 连接 / 切片状态
  • viewlogs:读最近 N 条日志

生产环境完全可以周期性采集 runstatus 做监控告警,不需要登进后端机器


# 快速索引

序号 SDK 方法 URL
64 heart / heart GET /heart
65 version / version GET /version
66 run_status / runStatus GET /api/v1/map/cmd/runstatus/_/v1?detail=true
67 view_logs / viewLogs GET /api/v1/map/cmd/viewlogs/_/v1?record=N

# 64 · 心跳 /heart

直接挂 Gateway 上(不走 /api/v1/... 前缀),用来判断进程是否活着。

# 响应(实采)

{
  "status": "ok",
  "timestamp": "2026-04-22T12:37:54.501381+08:00"
}
1
2
3
4

# Python / Node.js

svc.heart()
1
await svc.heart();
1

# 用作健康检查

# docker-compose healthcheck 片段
healthcheck:
  test: ["CMD", "curl", "-f", "http://127.0.0.1:27660/heart"]
  interval: 30s
  timeout: 3s
  retries: 3
1
2
3
4
5
6

# 65 · 版本 /version

# 响应(实采)

{
  "arch":    "amd64",
  "os":      "windows",
  "status":  "ok",
  "time":    "2026-04-22 12:37:54",
  "version": "2026041001"
}
1
2
3
4
5
6
7
  • version 是 service 的 build id(yyyyMMDD + 计数)
  • time 是服务器当前时间,可用来校时
  • 图形服务的版本在 runstatus → application.version 里(本例是 "20260305"

# 66 · 运行状态 runstatus

# 查询参数

字段 默认 说明
detail false true 时额外返回 activeRequests 列表

# 响应(实采 detail=true

{
  "application": {
    "pid":                   "11996",
    "runtime":               24063,
    "taskActiveThreadCount": 0,
    "threadId":              "12000",
    "version":               "20260305"
  },
  "process": {
    "activeProcesss":            {},
    "activeRequestCount":        0,
    "activeRequests":            [],
    "curParallelProcessGeomNum": 0,
    "maxParallelProcessGeomNum": 2,
    "recycleProcesss":           [],
    "reqTimeout":                600000,
    "waitWorkMapIds":            []
  },
  "slice": {},
  "store": {
    "connectionNames":        "_cache_map",
    "storeLastAccessTime":    [],
    "storeNoActiveCloseTime": 60,
    "storeStatus":            []
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 字段速查

字段 含义
application.runtime 进程启动毫秒数
process.activeRequestCount 当前在处理的请求数
process.activeRequests detail=true 时的 URL 列表,排查"卡在什么请求上"
process.curParallelProcessGeomNum 当前并行几何任务
process.maxParallelProcessGeomNum 并发上限
process.recycleProcesss 被回收的子进程
slice 切片缓存状态(按 layer)
store KV 存储连接

# Python

status = svc.run_status(detail=True)
assert status["application"]["pid"]
print(status["process"]["activeRequestCount"], "个并发请求")
1
2
3

# Node.js

const st = await svc.runStatus(true);
1

# 67 · 查看日志 viewlogs

# 查询参数

字段 默认 说明
record 100 读取最近多少条记录

# 响应(实采,部分片段)

{
  "logs": [
    "       *** log260422_0x115f4_2026-04-22.log ***  ",
    "[2026-04-22 11:58:01.625] [daily_logger] [info] File:() Line:(0) Msg: (\"process app started..._null_@>v1_1571__||__11996__||__28011__||__D:/Program Files/vjmapserver/data\")\n",
    "[2026-04-22 11:58:01.697] [daily_logger] [info] File:() Line:(0) Msg: (openMap begin... \"_null_@>v1\")\n",
    ...
  ]
}
1
2
3
4
5
6
7
8

每一条都是一行日志(带行尾 \n);多子进程时会用 *** log....log *** 分隔不同文件。

# Python

lines = svc.view_logs(record=200)["logs"]
for line in lines[-20:]:
    print(line.rstrip())
1
2
3

# Node.js

const { logs } = await svc.viewLogs(200);
1

⚠️ 这是最近 N 条,不是整个日志文件;想做审计请定时拉并本地存档。


# 监控样例:Python 拉取 → Prometheus

from time import sleep
def poll():
    while True:
        try:
            s = svc.run_status(detail=False)
            metrics = {
                "vjmap_up":             1,
                "vjmap_active_reqs":    s["process"]["activeRequestCount"],
                "vjmap_runtime_ms":     s["application"]["runtime"],
                "vjmap_store_count":    len(s["store"].get("storeStatus") or []),
            }
        except Exception:
            metrics = {"vjmap_up": 0}
        push_to_prometheus(metrics)
        sleep(15)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


# api/12-service-misc.md

# /service 杂项

分类代号:C.12 本章共 3 个接口

这里汇集了"不方便归到其它分类、但二开很常用"的三个能力:

  • QR Code 生成:把字符串直接渲染成 PNG 二维码
  • 字体字形 (PBF):vjmap地图渲染依赖的字体文件
  • 图片上传:把任意图片文件塞到服务器指定路径(配合前端水印/图床)

# 快速索引

序号 SDK 方法 URL
68 qrcode_url / qrcode_bytes / qrcodeUrl / qrcodeBytes GET /api/v1/service/qrcode?content=...&size=
69 glyphs_url / glyphs_bytes / glyphsUrl / glyphsBytes GET /api/v1/service/fonts/{fontstack}/{range}.pbf
70 upload_img / uploadImg POST /api/v1/service/uploadImg (multipart)

# 68 · 二维码 qrcode

# 查询参数

字段 默认 说明
content -(必填) 要编码的字符串;后端会 decodeURIComponent,所以不用双重编码
size 256 图片边长(像素)

# 响应

直接返回 image/png 字节,不是 JSON

# SDK 两种用法

  1. URL 构造(适合前端直接 <img src=...>):
url = svc.qrcode_url("https://example.com/project/42", size=512)
# http://127.0.0.1:27660/api/v1/service/qrcode?content=https%3A%2F%2Fexample.com%2Fproject%2F42&size=512&token=<jwt>
1
2
  1. 下载字节(适合服务端生成后转发/保存):
png = svc.qrcode_bytes("VJMAP", size=128)
open("qr.png", "wb").write(png)
1
2

Node.js:

const url = svc.qrcodeUrl('VJMAP', 512);
const buf = await svc.qrcodeBytes('VJMAP', 128);
1
2

# 实采信息

// qrcode_bytes_info.json
{ "len": 284, "md5": "2577b1157cf93feefce8c6b180a0ff42", "magic_ok": true }
1
2

# 69 · 字体 fonts/{fontstack}/{range}.pbf

vjmap 矢量渲染在画文字时,需要按需下载对应字体、对应 Unicode 区间的 PBF 字形文件。这个接口就是字形的分发端。

# 路径参数

字段 说明
fontstack 字体名,可用 , 分隔多个回退栈。后端会依次尝试,并自动在末尾追加 Arial Unicode MS Regular 作为兜底
range Unicode 字形区间,形如 0-255256-511。通常是 256 个字形一包

# 响应

application/x-protobuf 字节(PBF)。

# SDK

url = svc.glyphs_url("Arial Unicode MS Regular", "0-255")
pbf = svc.glyphs_bytes("Arial Unicode MS Regular", "0-255")
1
2
const url = svc.glyphsUrl('Arial Unicode MS Regular', '0-255');
1

# 实采

// glyphs_bytes_info.json
{
  "len": 75282,
  "md5": "638faf3bf9a90424d23513cfab7e9ce1"
}
1
2
3
4
5

# 用在 vjmap 里

map.setStyle({
  glyphs: 'http://127.0.0.1:27660/api/v1/service/fonts/{fontstack}/{range}.pbf?token=' + TOKEN,
  // ...其它
});
1
2
3
4

注意 {fontstack}/{range}vjmap 自己替换的占位符,别让 URL 编码器把 { / } percent-encode 掉。


# 70 · 上传图片 uploadImg

# 特殊鉴权

这个接口不走普通 token,而是检查 HTTP header:

Authorization: Basic X3ZqbWFwQF8=
1

即 Basic base64("_vjmap@_")。后端硬编码。SDK 默认已经带上;你也能传自定义 authorization 参数覆盖。

# Headers

Header 说明
Authorization 上述固定值(SDK 默认)
Uri 必填:服务器端保存目录(以 / 结尾)。SDK 参数名 uri
watermark "1" 时后端自动叠加水印(utils.NewWatermarkBase64 实现)

# multipart 字段

字段 说明
upload 文件内容。注意 field name 是 upload 不是 file

# 响应

成功(实采):

{
  "code": 0,
  "path": "./data/test_upload/nb_upload_smoke.png",
  "url":  "nb_upload_smoke.png"
}
1
2
3
4
5

失败(实采:错误 Authorization):

{
  "code": 7,
  "data": {},
  "msg": "no Authorization"
}
1
2
3
4
5

# Python

# 从本地文件上传
svc.upload_img(
    "./avatar.png",
    uri="./data/upload/avatars/",
    filename="user_42.png",
    watermark=True,
)

# 从字节上传
svc.upload_img(
    ("user_42.png", png_bytes),
    uri="./data/upload/avatars/",
)
1
2
3
4
5
6
7
8
9
10
11
12
13

# Node.js

// Node 18+
await svc.uploadImg('./avatar.png', {
  uri: './data/upload/avatars/',
  filename: 'user_42.png',
  watermark: true,
});

// Buffer
await svc.uploadImg(buf, {
  uri: './data/upload/avatars/',
  filename: 'user_42.png',
});
1
2
3
4
5
6
7
8
9
10
11
12

# api/13-ai.md

# 13. AI(向量检索 / FAQ / 文档)

条件启用:这一组接口依赖 vjmap 服务端的嵌入(embedding)模型与向量库。 若未正确配置,调用会返回 500(例如缺少 bge-large-zh-v1.5-f16.gguf 模型文件)。 在你的环境若未开启 AI 子服务,直接跳过本章。

本章覆盖 3 个接口:

No. 接口 URL Method 说明
13.1 vectorSearch /api/v1/service/vectorSearch GET / POST 相似性检索
13.2 chatFaq /api/v1/service/chatFaq GET 获取 FAQ 知识库
13.3 vectorDocs /api/v1/service/vectorDocs GET 获取向量库已有文档元数据

# 13.0 启用探针

SDK 提供 aiEnabled() / ai_enabled()(内部调用 chatFaq)判断 AI 子服务是否可用; 失败(抛 VjmapError)则视为未启用,跳过后续调用即可。

if not svc.ai_enabled():
    raise SystemExit("AI 未启用,略")
1
2

# 13.1 vectorSearch — 相似性检索

按给定自然语言 query 在向量库中检索最相似的若干条文档。

# 请求

两种请求方式:

方式 URL Body 备注
GET GET /api/v1/service/vectorSearch?query=...&numDocuments=... 简单场景用
POST(推荐) POST /api/v1/service/vectorSearch JSON 支持 documents 等复杂字段

# 主要参数

字段 类型 默认 说明
query string 必填 查询文本
numDocuments int 20 返回条数
collection string "vj" 向量库集合名
where string metadata 过滤表达式(chromem-go (opens new window) 语法)
whereDocument string document 内容过滤表达式
searchDescription string 附加搜索上下文,帮助模型更精准
disableSplitSentence bool false 关闭"先分句再分别 embed"
disableSearchKnowledge bool false 仅搜 documents,不搜知识库
documents array 临时附带的用户文档(不会持久化)。每项:{content:"...", metadata:{...}}

# 响应

{
  "status": "ok",
  "data": [
    {
      "id": "__createPolygon",
      "content": "根据给定的点坐标数组直接绘制一个多边形",
      "metadata": { "...": "..." },
      "similarity_score": 0.8273
    },
    { "...": "..." }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12

# SDK 示例

res = svc.vector_search("如何画多边形", num_documents=5)
for doc in res.get("data", []):
    print(doc["id"], doc.get("similarity_score"))
1
2
3
const res = await svc.vectorSearch('如何画多边形', { numDocuments: 5 });
res.data.forEach(d => console.log(d.id, d.similarity_score));
1
2

# 本地实测(失败样例)

当前测试环境缺少 embedding 模型文件,真实请求返回:

{
  "status": 500,
  "body": {
    "code": 8,
    "msg": "D:/Program Files/vjmapserver/data/ai/models/bge-large-zh-v1.5-f16.gguf is not exist"
  }
}
1
2
3
4
5
6
7

部署提示:部署方需根据官方文档下载 embedding 模型文件,放置于 服务端的 data/ai/models/ 目录下,并在配置中指定模型名。未配置时, vectorSearch 会失败但不影响其他子系统运行。


# 13.2 chatFaq — FAQ 知识库

# 请求

GET /api/v1/service/chatFaq
1

# 响应

返回一个按"主题"分组的字符串数组字典。

{
  "status": "ok",
  "data": {
    "UI交互": [
      "弹出学生表单输入对话框,包括姓名、年龄(默认18)、性别",
      "获取地图的当前中心点坐标X和Y、地图级别,生成json对象,根据这数据弹出数据信息框..."
    ],
    "信息窗口": [
      "在坐标[30,50]处添加一个信息窗口,内容为\"Hello,World\""
    ],
    "几何算法": [ "..." ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

数据源来自服务端 ./data/ai/faq/*.md,并开启 5 分钟 HTTP 缓存。

# SDK 示例

faq = svc.chat_faq()
for topic, items in faq["data"].items():
    print(f"- {topic}: {len(items)} 项")
1
2
3
const { data } = await svc.chatFaq();
for (const [topic, items] of Object.entries(data)) {
  console.log(`- ${topic}: ${items.length}`);
}
1
2
3
4

# 13.3 vectorDocs — 向量库文档

# 请求

GET /api/v1/service/vectorDocs
1

# 响应

返回一个数组,每项是知识库里的一条文档元数据:

{
  "status": "ok",
  "data": [
    {
      "id": "__createPolygon",
      "content": "根据给定的点坐标数组直接绘制一个多边形",
      "metadata": {
        "category": "draw",
        "code": "...",
        "comments": "...",
        "function": "__createPolygon",
        "param": "..."
      },
      "similarity_score": 0
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

similarity_score 恒为 0(此接口不做相似度计算,仅导出文档)。

# SDK 示例

docs = svc.vector_docs()["data"]
print("知识库条目数:", len(docs))
print("分类统计:", {
    c: sum(1 for d in docs if d["metadata"].get("category") == c)
    for c in sorted({d["metadata"].get("category", "") for d in docs})
})
1
2
3
4
5
6
const { data: docs } = await svc.vectorDocs();
console.log('条目数:', docs.length);
1
2

# 使用建议

  1. 部署前先跑探针:初始化客户端后先调 chat_faq(),失败则关闭 AI UI 入口。
  2. 前端搜索:优先用 POST vectorSearch,把 searchDescription 带上业务上下文能显著提升命中率。
  3. 冷启动:第一次调用 vectorSearch 会加载 embedding 模型,响应可能秒级; 第二次起基本在毫秒级。
  4. 安全vectorDocs 直接返回所有文档内容(含 code 字段),不要把它暴露给未鉴权用户。