Files
tamakyi 30c502ce7b 备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」:

- 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上
  用绿/灰/红标出会新增 / 不变 / 会丢的区域
- 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人),
  新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行;
  合入的行写回 entity_raw,保证再传同一份备份时差异归零
- 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的
  到访全变成假冲突
- 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联;
  访客票没有 dataset_id,回填按活跃数据集认领
- 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释

导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
2026-09-19 16:04:00 +08:00

91 lines
2.9 KiB
Go

package fog
// Diff 是一份待导入快照与当前迷雾之间的差异画像。
//
// 导入有两种语义完全不同的模式:叠加只做按位或、不可能丢数据;整体替换会先清空,
// 快照里没有的块一律消失。以前两种模式在界面上只是一个下拉框,站长在下手之前
// 看不到「这次能补多少」和「这次会丢多少」,实测里整体替换静默丢掉过 14% 的已探索区域。
type Diff struct {
CurBlocks, CurCells int // 库里现有
IncBlocks, IncCells int // 快照本身
// GainBlocks/GainCells 是叠加模式真正新增的部分:快照里当前完全没有的整块,
// 加上重叠块里补亮的格。
GainBlocks int
GainCells int
// OverlapBlocks 是与现有块坐标相同的块数,其中 SameBlocks 是一格都不会变的。
OverlapBlocks int
SameBlocks int
// LostBlocks/LostCells 是快照里完全没有、只有整体替换才会消失的部分。
LostBlocks int
LostCells int
curAreaM2, incAreaM2, gainAreaM2, lostAreaM2 float64
}
// DiffBlocks 比对当前块集与一份待导入快照。入参块集不要求有序。
func DiffBlocks(cur, inc []Block) Diff {
var d Diff
d.CurBlocks, d.IncBlocks = len(cur), len(inc)
curMap := make(map[[2]int][]byte, len(cur))
for _, b := range cur {
curMap[[2]int{b.GX, b.GY}] = b.Bitmap
n := popcountSum(b.Bitmap)
d.CurCells += n
d.curAreaM2 += float64(n) * cellAreaM2(b.GX, b.GY)
}
incSet := make(map[[2]int]struct{}, len(inc))
for _, b := range inc {
incSet[[2]int{b.GX, b.GY}] = struct{}{}
n := popcountSum(b.Bitmap)
d.IncCells += n
d.incAreaM2 += float64(n) * cellAreaM2(b.GX, b.GY)
old, ok := curMap[[2]int{b.GX, b.GY}]
if !ok {
d.GainBlocks++
d.GainCells += n
d.gainAreaM2 += float64(n) * cellAreaM2(b.GX, b.GY)
continue
}
d.OverlapBlocks++
gain := 0
for i := range b.Bitmap {
gain += int(popcount[b.Bitmap[i]&^old[i]])
}
if gain == 0 {
d.SameBlocks++
continue
}
d.GainCells += gain
d.gainAreaM2 += float64(gain) * cellAreaM2(b.GX, b.GY)
}
for _, b := range cur {
if _, ok := incSet[[2]int{b.GX, b.GY}]; ok {
continue
}
n := popcountSum(b.Bitmap)
d.LostBlocks++
d.LostCells += n
d.lostAreaM2 += float64(n) * cellAreaM2(b.GX, b.GY)
}
return d
}
func (d Diff) CurAreaKm2() float64 { return d.curAreaM2 / 1e6 }
func (d Diff) IncAreaKm2() float64 { return d.incAreaM2 / 1e6 }
func (d Diff) GainAreaKm2() float64 { return d.gainAreaM2 / 1e6 }
func (d Diff) LostAreaKm2() float64 { return d.lostAreaM2 / 1e6 }
// MergeAreaKm2 是叠加后的总量,CoverAreaKm2 是整体替换后的总量。
func (d Diff) MergeAreaKm2() float64 { return (d.curAreaM2 + d.gainAreaM2) / 1e6 }
func (d Diff) CoverAreaKm2() float64 { return d.incAreaM2 / 1e6 }
// Changed 报告叠加是否会让地图有任何变化——为假说明这份快照已经被完全覆盖过。
func (d Diff) Changed() bool { return d.GainCells > 0 }