Mapbox 围栏编辑踩过的几个坑:key 串了、形状炸了、icon 挡住绿点

如果你也在用 React + Mapbox 做可编辑围栏(画多边形、拖顶点、弹属性面板),下面这几个现象可能眼熟:

  • 两块围栏名字一样,点 A 再点 B,右边属性面板还是 A 的内容
  • 切到另一套坐标系的地图时,多边形突然被撕开、经纬度飞到天边
  • 分时 / 等级那种小图标正好压在编辑手柄上,绿点点不到

1. 同名围栏一切换,属性面板串了

咋回事

面板大概长这样:

tsx 复制代码
<InfoModal
  selectedPolygon={selectedPolygon}
  key={
    selectedPolygon.index +
    selectedPolygon.polygon.properties.draw_type +
    selectedPolygon.polygon.properties.name
  }
/>

看起来挺合理:换一个就换 key,组件重建。

问题是 name 可以重复。两块围栏同名、同类型时,拼出来的 key 一模一样,React 觉得还是同一个实例,内部表单状态就不会清。

咋改

key 跟业务主键走,别用展示字段拼:

tsx 复制代码
key={String(selectedPolygon.polygon.id)}

还有一个容易忽略的:选中切换是异步的。属性 atom 更新时,地图上「当前选中」可能已经是下一块了,id 对不上还继续 setFeatureProperty,等于把 A 的字段写进 B。回写前加一句守卫就行:

tsx 复制代码
if (String(polygonAttributes.id) !== String(lastSelectedPolygon.id)) return

顺带提一嘴:两套地图来回切的时候,如果上一张已经算好了当前视口,下一张就别再无脑 flyTo 多边形中心。用户刚看好的位置被抢走,体验很差。有切换视口就保留,没有再居中。

2. 坐标转换之后,多边形形状爆炸

这个最离谱,也最值得单独说。

现象

编辑侧几何看着正常,一切到展示用的 lng/lat 地图,整块围栏偶发「撕开」。不是每次都炸,反复拖、反复裁更容易撞上。

一开始会怀疑投影超界、接口转错、裁剪把几何裁坏了。把数据坐标 dump 出来看,其实一直是对的------炸在展示转换。

真正的原因

GeoJSON 多边形是闭合环,很多人(包括 turf 裁完再 nudge)会这么写:

ts 复制代码
ring[ring.length - 1] = ring[0]  // 首尾绑成同一个数组引用

然后你调用库里的「整 Feature 转换」,比如类似:

ts 复制代码
crsControl.toLngLatByFeature(feature)

内部常见实现是:cloneDeep 一下,再对每个点 splice 原地改坐标

坑在这:cloneDeep 只复制结构,不断开别名

ring[0] === ring[last] 在 clone 之后还是 true

遍历时:

  1. 第一个点:投影 → 展示坐标,比如 [-118, 83]
  2. 轮到末尾:还是同一个数组,把已经转好的展示坐标再当投影喂进去 → 变成 [-221, 2.6] 这种鬼东西

经度冲出 ±180,Mapbox 再一环绕,肉眼就是形状爆炸。

中间顶点都正常,只有首尾被转了两遍------对照日志打一下就很明显。

咋改

别信「内部已经 clone 了」的整 Feature API。逐点转,每次 new 一个新数组:

ts 复制代码
export const featureToLngLat = (features, crsControl) => {
  return features.map((feature) => {
    const srcRings = feature.geometry.coordinates
    return {
      ...feature,
      geometry: {
        ...feature.geometry,
        type: 'Polygon',
        coordinates: srcRings.map((ring) =>
          ring.map((pos) => crsControl.toLngLat([pos[0], pos[1]])),
        ),
      },
    }
  })
}

反向转 UTM 同理。从机制上杜绝二次转换,不依赖库内部 clone 行为。

如果暂时必须原地改,至少先把首尾断引用:

ts 复制代码
ring[ring.length - 1] = [...ring[0]]

以后凡是对 GeoJSON 做逐点变换,我都会先看一眼:ring[0] === ring[last] 是不是 true。是的话就危险。

3. 小图标挡住了编辑绿点

现象

顶点上要挂一个小 icon(分时、风险等级之类)。坐标落在 ring[ring.length - 2],Mapbox 默认 icon-anchor 是中心------图片正中对准顶点,编辑时的绿色拖拽手柄经常被盖住。

试过又改回去的方案

第一反应是把 icon 挪到外接矩形中心。绿点是腾出来了,但 icon 离围栏角点太远,看起来不像「属于这块围栏」。

更干净的做法

坐标还放顶点,改 layout:

ts 复制代码
layout: {
  'icon-allow-overlap': true,   // 角点挤在一起别被碰撞检测藏掉
  'icon-ignore-placement': true,
  'icon-anchor': 'bottom',      // 底边对齐坐标,图片浮在顶点上方
}

Mapbox symbol 的「锚点坐标」和「你眼睛看到的中心」不是一回事。很多遮挡问题,调 icon-anchor / icon-offset 比硬改几何点舒服。

写在最后

这几件事乍一看像地图玄学,掰开都是很普通的前端问题:

  • React 拿展示字段当 key → 同名复用错实例
  • 异步状态回写不做 id 校验 → 串写
  • 闭合环首尾同引用 + 原地坐标变换 → 点被转两遍
  • symbol 默认居中锚点 → 挡住交互手柄

做围栏编辑的同学,坐标转换那条建议自己 dump 一次:同一份 Feature,中间点和首尾点分别打转换前后的值,再打一下 ring[0] === ring[last]。很多「偶现撕开」其实一眼就能钉死。

相关推荐
SGAMERrain1 小时前
做了一个免费的在线绘画网格工具 DrawGrid,聊聊一个小工具是怎么越做越完整的
前端
我家猫叫佩奇1 小时前
🦭 厌倦了千篇一律的线性图标?Naive Icons 正式开源
前端·javascript·css
LEE2 小时前
前端转型全栈 00:AI 时代该学哪些,不该学哪些
前端·后端
晚安日记wanna2 小时前
SSR 为什么不适合登录态从水合冲突到缓存串号
前端·react.js·面试
aixingpan2 小时前
aixingpan.cn API开发文档:api_docs_bichart_natalvssolararc2接口指南
前端·php
无限压榨切图仔2 小时前
一个优惠券需求改了三端,我才明白全栈不是多学一门语言
前端·后端
晚安日记wanna2 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
晚安日记wanna2 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
用户921080262862 小时前
前端 Vue 专栏 09:Vue Router 与前端路由
前端