# split-map **Repository Path**: GISuser/split-map ## Basic Information - **Project Name**: split-map - **Description**: 基于拖动的方式分割图形 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-06 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 地图图形按比例分割 Demo 基于 `maplibre-gl.js` + `turf.js` 的单文件 Demo:在地图上选中地块(或自己绘制图形),画一条分割线(支持折线),按面积比例把图形切成 2 块。 ## 运行 直接双击 `index.html` 用浏览器打开即可(依赖 CDN:maplibre-gl 4.7.1、@turf/turf 7.1.0)。 若浏览器限制 `file://` 下的外部请求,可起一个本地静态服务: ```powershell cd d:\WebProject\split-demo python -m http.server 8080 # 然后访问 http://localhost:8080/index.html ``` ## 操作流程 1. 地块图层默认允许选中:点击地块即可选中(高亮为绿色虚线)。 2. 右侧工具栏:`绘制图形` / `按比例分割` / `重置`。 3. `绘制图形`:点击地图添加顶点,**双击结束**(至少 3 点)。 4. 选中地块或绘制图形后,点击 `按比例分割`,在地图上连续点击添加分割线顶点(支持折线),**双击结束绘制**(至少 2 个点)。 5. 双击完成后弹出面板,显示选中图形总面积与 A / B 两块面积、比例;**初始预览直接按你画的折线(平移量 `t = 0`)切割**,A/B 面积、占比与滑块值都由这一次切割结果回填,所以刚打开面板时红线与折线逐点重合(含每个折点),不再是固定 50%。只有折线本身切不出两块时才回退 50%,并在提示条说明原因。 6. 完成后,**用户画的分割折线一直保留**(深色描边 + 亮色虚线 + 各顶点),作为位置参考。 7. 拖动比例滑块(或直接修改 A/B 面积),整条折线沿**首尾连线的垂直方向**自动平移,用**红色虚线**表示;平移时折线形状与每个折点都保持不变,红线就是 A/B 两块的公共边,因此**红线既与画的线形状一致,又与 A/B 分界线重合**(不会错位)。折线回勾或自交时的例外见「已知限制」。两块图形上以气泡标注实时显示面积(亩)与占比,并随线移动。 8. 点击 `确定分割` 完成切成 2 块并关闭面板;`取消` 放弃本次分割。 9. 按 `Esc` 可随时退出当前模式。 ## 实现要点 - 平移方向取折线起点 → 终点连线的方向 `d`,左法向 `n = (-d.y, d.x)` 指向区域 A;折线整体沿 `n` 平移 `t`,线形(含每个折点)严格不变。 - A 侧区域是「平移后的折线沿统一法向 `n` 扫掠到固定上界 `TOP`」围成的区域:下边界就是折线本身(含每个折点),凹角、凸角都按折线原样保留,不会被抹平;上界不随 `t` 平移,因此 A 面积关于 `t` 严格递减、二分稳定。 - 折线首末段会沿自身方向外延,并沿首尾连线方向再推一段(`extendCutLine()`),保证两端都超出图形边界;端点落在图形内部时也能切开,外延段同样参与分界,红线与 A/B 边界保持一致。 - 面板初始化走 `splitAtDrawnLine()` → `splitAtT(ctx, 0)`,直接生成 A/B 两块、红色切缝与面积;初始滑块值由 `areaA / total` 直接回填连续值(不取整),不再另算一次比例,避免「算比例」与「算预览」两套结果不一致。 - 比例是连续值:滑块 `step="any"`,初始比例与手输面积反算的比例都不再按 0.5% 取整,仅保留 `0.5~99.5` 的有效边界;面板标签与气泡占比统一显示 2 位小数(内部仍按连续值计算)。面积输入 `step="0.01"` 与 `fmtMu()` 两位小数回写保持不变,即面积粒度仍为 0.01 亩,手输面积经预览重算回写后可能与输入值相差 ±0.01 亩。 - 拖动滑块或修改面积时,才用 34 次二分查找求出使 A 面积等于目标比例的 `t`,再交给同一个 `splitAtT()` 出图;初始预览与滑块预览共用 `showSplitResult()` 渲染。 - `splitAtT()` 会校验 A/B 面积有效;切不出两块时返回错误,面板初始化回退 50% 并明确提示,不再静默吞掉异常。 - 分割线绘制支持任意个顶点:`state.cutPts` 保存折线顶点,`state.cutDone` 标记双击完成;双击会连带触发两次 `click`,用「与上一点屏幕距离小于 8px 忽略」去重,并在完成时剔除尾部重复点。 - 点数不足 2 个、或首尾点重合时,只提示不打开面板;`Esc` / `取消` / `重置` 会清空折线与完成状态。 - 用户折线用双层线样式(深色描边 + 亮色虚线)渲染在 `cut-src` 上,红色切缝仍在独立的 `seam-src` 图层并置于上层,互不覆盖。 - `A = 图形 ∩ A 侧区域`,`B = 图形 − A 侧区域`(`turf.intersect` / `turf.difference`,均传 `FeatureCollection`);两块互补,公共边即分界线。 - 折线沿首尾连线方向单调时,A 侧区域是单个简单多边形;凹多边形、带孔多边形同样适用(已覆盖凹形、带孔、斜线、回勾、自交等用例验证,面积误差 < 0.01%)。 - 面积使用 `turf.area`(测地面积,单位 ㎡),界面以**亩**展示(1 亩 ≈ 666.67 ㎡)。 - 切缝(A/B 分界线)由“平移后的折线逐段裁剪到图形内”得到:每段与图形边界求交点、按段内参数切分、用子段中点判断是否在图形内,首尾相接的子段合并;凹图形 / 带孔图形会得到多段。因此切缝形状与你画的折线完全一致,红线不会退化成一刀直切。折线回勾 / 自交时改用 `sharedBoundarySegments()` 从 A/B 真正重合的边界生成红线,线与面仍然严格重合。该结果渲染为独立的红色图层 `seam-src`,与用户画的线(`cut-src`)互不覆盖,用户线得以保留。 - 面积气泡是挂在地图容器里的普通 DOM 覆盖层,用 `map.project()` 手动定位、监听 `move` / `zoom` / `rotate` / `resize` 同步位置(不依赖 glyph 字体服务,中文正常显示、无额外请求)。 - 气泡落点取 `turf.pointOnFeature`(保证在图形内部),取不到时回退 `turf.centroid`。 - 地块来自矢量瓦片,被瓦片边界裁断时,用相同 `dkbm`/`id` 的条块 `turf.union` 合并还原完整图形。 ## 配置(`index.html` 顶部 `CFG`) | 配置项 | 说明 | | --- | --- | | `tdtScheme` | 天地图切片规则,默认 `tms`;若底图上下颠倒/错位改为 `xyz` | | `imgTiles` / `ciaTiles` | 天地图影像 / 注记瓦片地址(经 `tdt-proxy` 代理) | | `parcelTiles` | 地块矢量瓦片模板(`scheme=xyz`,`.pbf`) | | `parcelSourceLayer` | 矢量图层名,`dt_zzdk`(种植地块) | | `center` / `zoom` | 初始视图,默认定位到河南省濮阳市清丰县 `[115.1036, 35.8855]`,缩放 14 级 | ## 已知限制 - 地块矢量瓦片服务的 TileJSON 中 `tiles` 为 `http://gykj123.cn:9035/...`,本 Demo 直接写成 `https://agribigdata.com.cn/map9035/...` 模板;若该 HTTPS 地址不可用,可把 `CFG.parcelTiles` 换回 TileJSON 返回的 HTTP 模板(页面需用 HTTP 方式打开,避免混合内容拦截)。 - 分割结果为图上的两个新图形,再次选中新地块时预览图层会被清空。 - 分割结果不写回业务服务,仅前端演示。 - 折线形状会完整参与切割,但平移方向仍是首尾连线的垂直方向;折线回勾或自交时,A 侧按「折线沿 `n` 扫掠区域的下包络」判定,红线取 A/B 实际公共边而不再逐点等于所画折线,提示条会明确说明,亮色折线仅作位置参考。若折线自身切不出两块,初始预览会回退到 50% 并在提示条说明原因。