From fe7737b17574e6178c713c00689011d81a1291e4 Mon Sep 17 00:00:00 2001 From: gaozheng Date: Sun, 7 Jun 2026 17:35:38 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=99=BB=E5=BD=95=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E6=A0=B8=E5=AE=9E=20+=20=E6=A0=B7=E6=9C=AC=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 登录: JSEncrypt RSA-2048, login2(/admin/tenant/auth), token=data.accessToken - tools/validate_samples.py: 复现 #17 散点/#18 网格等值面, 验证解析+色阶+异常逻辑 - 量化两剖面几何(夹角77.7度,十字支撑), 佐证可信体数据依赖(需>=3线/3D网格) - spec §8 更新登录细节; _validate 产物已忽略 --- .gitignore | 1 + .../2026-06-07-geopro-desktop-m1-design.md | 4 +- tools/validate_samples.py | 126 ++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 tools/validate_samples.py diff --git a/.gitignore b/.gitignore index 09fc7c9..e50fd19 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ docs/proto*.jpeg docs/tenant_login*.jpeg docs/docx_media/ docs/_docx_media/ +docs/_validate/ # ---- Large redundant archive (sample data kept unpacked in folder) ---- docs/剖面网格数据的色阶数据2等文件.tar diff --git a/docs/superpowers/specs/2026-06-07-geopro-desktop-m1-design.md b/docs/superpowers/specs/2026-06-07-geopro-desktop-m1-design.md index b7b8811..165117e 100644 --- a/docs/superpowers/specs/2026-06-07-geopro-desktop-m1-design.md +++ b/docs/superpowers/specs/2026-06-07-geopro-desktop-m1-design.md @@ -221,6 +221,8 @@ IDatasetRepository { - **API 基址** `http://tenant.geomative.cn/pop-api`(openresty 反代;OpenAPI 的 `/admin/*`、`/business/*` 加 `/pop-api` 前缀)。 - **认证头** `geomativeauthorization: Geomative `(不透明会话令牌,非 JWT)。 - **登录三步**:① `GET /business/system/personalUser/getImageCode`→验证码图+`codeId` → ② `POST /business/system/personalUser/verifyCodeCheck {code,codeId}` → ③ `POST /admin/tenant/auth/login2 {username, password=RSA加密, checkCode}`→token。 +- **密码加密 = JSEncrypt RSA-2048**(前端 vendor 用 JSEncrypt 库;密文 base64 ~344 字符 = 256 字节)。token 取响应 **`data.accessToken`**(值即 `"Geomative "`,存 web localStorage `token`)。 +- 另有 `/email`、`/phone` 登录支线(非 M1)。 - 登录后:`getInfo` / `list-menus` / `enterprise/info` / `enterprise/joined/list`。 ### 8.2 实现要点 @@ -234,7 +236,7 @@ IDatasetRepository { 抓取的真实流程里**未见 refresh-token 实际使用,login2 只返不透明会话 token**。因此: -- **RSA 公钥来源**待确认(内置 vs 接口取)。 +- **RSA 公钥常量**待精确提取(机制已定:JSEncrypt RSA-2048;公钥在某懒加载 login 分包,实现登录时现场提取即可)。 - **token 生命周期 / 是否有 refresh 机制**待确认。据此二选一设计: - (a) 有 refresh token → 标准静默刷新、401 静默续期。 - (b) 仅会话 token → 「免登录」= 持久化会话 token 至其有效期;**到期/401 引导用户重新登录(含验证码),不声称静默重登**。 diff --git a/tools/validate_samples.py b/tools/validate_samples.py new file mode 100644 index 0000000..0fdcc6b --- /dev/null +++ b/tools/validate_samples.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +样本数据离线验证 / 渲染基准生成器。 + +用途:在未搭好 Qt/VTK 环境前,用 Python(matplotlib) 复现剖面散点(#17)、网格等值面(#18) +渲染效果,验证「样本解析 + colorBar 色阶映射 + 异常叠加」逻辑正确;并量化两条剖面的几何 +关系,为 dd_voxel 可信度(设计 §10/§14 K-10)提供依据。 + +产出的 PNG 作为后续 VTK 渲染的「地面真值」对照图。 + +运行: + python tools/validate_samples.py +输出:docs/_validate/ref_17_scatter.png, ref_18_grid.png(该目录已 .gitignore) + +依赖:numpy, matplotlib +""" +import json +import os +import re +import sys + +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap, BoundaryNorm + +DATA = os.path.join(os.path.dirname(__file__), "..", "docs", "剖面网格数据的色阶数据2等文件") +OUT = os.path.join(os.path.dirname(__file__), "..", "docs", "_validate") + + +def load(name): + with open(os.path.join(DATA, name), encoding="utf-8") as f: + return json.load(f)["data"] + + +def parse_color(c): + """支持 #RRGGBB 与 rgba(r,g,b,a);返回 0-1 RGB。""" + c = c.strip() + if c.startswith("#"): + return (int(c[1:3], 16) / 255, int(c[3:5], 16) / 255, int(c[5:7], 16) / 255) + nums = re.findall(r"[\d.]+", c) + return (float(nums[0]) / 255, float(nums[1]) / 255, float(nums[2]) / 255) + + +def colorbar(cb): + """colorBar: [[value, color], ...] -> (sorted values, colors)。""" + pairs = sorted(((float(v), parse_color(c)) for v, c in cb), key=lambda p: p[0]) + return np.array([p[0] for p in pairs]), [p[1] for p in pairs] + + +def render_scatter(): + raw = load("剖面原数据1.txt") + vals, cols = colorbar(load("剖面原数据的色阶数据1.txt")["properties"]["colorBar"]) + x, y, v = np.array(raw["xlist"]), np.array(raw["ylist"]), np.array(raw["vlist"]) + cmap = ListedColormap(cols) + norm = BoundaryNorm(np.append(vals, vals[-1] * 1.2), cmap.N) + plt.figure(figsize=(11, 3.2)) + plt.scatter(x, y, c=v, cmap=cmap, norm=norm, s=6, marker="s") + plt.gca().invert_yaxis() + plt.title("scatter (raw profile) ~#17") + plt.colorbar(shrink=0.7) + plt.tight_layout() + plt.savefig(os.path.join(OUT, "ref_17_scatter.png"), dpi=90) + plt.close() + print(f"[#17] pts={len(x)} x[{x.min():.1f},{x.max():.1f}] " + f"y[{y.min():.1f},{y.max():.1f}] v[{v.min():.1f},{v.max():.1f}]") + + +def render_grid(): + g = load("剖面网格数据1.txt") + vals, cols = colorbar(load("剖面网格数据的色阶数据1.txt")["properties"]["colorBar"]) + gx, gy, gv = np.array(g["x"]), np.array(g["y"]), np.array(g["v"]) # gv: [22][100] + X, Y = np.meshgrid(gx, gy) + plt.figure(figsize=(11, 3.0)) + cf = plt.contourf(X, Y, gv, levels=vals, colors=cols[:len(vals) - 1]) + plt.contour(X, Y, gv, levels=vals, colors="k", linewidths=0.4) + for a in load("剖面网格数据1——对应的异常圈定数据.txt"): + coord = a.get("location", {}).get("coordinate", []) + if coord and isinstance(coord[0], dict): + plt.plot([p["x"] for p in coord], [p["y"] for p in coord], "k--", lw=1.5) + plt.gca().invert_yaxis() + plt.title("grid bands + contour + anomaly ~#18") + plt.colorbar(cf, shrink=0.7) + plt.tight_layout() + plt.savefig(os.path.join(OUT, "ref_18_grid.png"), dpi=90) + plt.close() + print(f"[#18] grid V{gv.shape} vmin/max {g['vmin']:.1f}/{g['vmax']:.1f}") + + +def analyze_voxel_geometry(): + """量化两条剖面几何关系:判断 dd_voxel 三维插值的数据支撑充分性。""" + def line(name): + r = load(name) + return np.column_stack([r["projectXList"], r["projectYList"]]) + + def principal_dir(p): + c = p - p.mean(0) + _, _, vt = np.linalg.svd(c, full_matrices=False) + return vt[0], p.mean(0) + + p1, p2 = line("剖面原数据1.txt"), line("剖面原数据2.txt") + d1, c1 = principal_dir(p1) + d2, c2 = principal_dir(p2) + ang = np.degrees(np.arccos(abs(np.clip(d1 @ d2, -1, 1)))) + n1 = np.array([-d1[1], d1[0]]) + perp_gap = abs((c2 - c1) @ n1) + print(f"[voxel] line1 pts={len(p1)} line2 pts={len(p2)} | " + f"angle={ang:.1f}deg perp_gap={perp_gap:.1f}m " + f"=> {'crossing' if ang > 30 else 'sub-parallel'}; " + f"volume between lines is interpolated/extrapolated " + f"(credible voxel needs >=3 lines or a 3D grid)") + + +def main(): + os.makedirs(OUT, exist_ok=True) + render_scatter() + render_grid() + analyze_voxel_geometry() + print("written:", sorted(os.listdir(OUT))) + + +if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + main()