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