备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」: - 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上 用绿/灰/红标出会新增 / 不变 / 会丢的区域 - 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人), 新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行; 合入的行写回 entity_raw,保证再传同一份备份时差异归零 - 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的 到访全变成假冲突 - 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联; 访客票没有 dataset_id,回填按活跃数据集认领 - 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释 导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
package fog
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
)
|
||||
|
||||
// 差异图的三种归属。改这里的色值要同步 templates/admin/fog-preview.html 的图例。
|
||||
var (
|
||||
DiffBothColor = [4]byte{126, 134, 146, 148} // 两边都有:导入不影响
|
||||
DiffGainColor = [4]byte{36, 164, 90, 205} // 只有快照有:叠加会新增
|
||||
DiffLostColor = [4]byte{214, 56, 56, 210} // 只有库里有:整体替换会丢
|
||||
)
|
||||
|
||||
const (
|
||||
diffNone uint8 = iota
|
||||
diffBoth
|
||||
diffGain
|
||||
diffLost
|
||||
)
|
||||
|
||||
// RenderDiffTile 渲染一张 256x256 的差异瓦片,供站长在真实底图上核对
|
||||
// 「这份快照能补上哪里」与「整体替换会丢掉哪里」。
|
||||
//
|
||||
// 与 RenderTile 一样按底图坐标系做 WGS84→GCJ02 纠偏;不做描边——三种颜色
|
||||
// 已经够区分,再描边在粗缩放级下会糊成一片。
|
||||
//
|
||||
// 逐格判定只在放大到「一格不小于一像素」(z>=14)时做;更粗的级别下一个块还不到
|
||||
// 一个像素,按块内占多数的类别整块涂色。绘制顺序 both→gain→lost,让「会新增」
|
||||
// 和「会丢」压住底色,重叠处不至于被灰吃掉。
|
||||
func RenderDiffTile(cur, inc map[[2]int][]byte, z, x, y int, crs string) *image.NRGBA {
|
||||
if z < 0 || z > 22 {
|
||||
return nil
|
||||
}
|
||||
step := WorldCells >> z
|
||||
scale := 256.0 / float64(step)
|
||||
ox, oy := float64(x*step), float64(y*step)
|
||||
|
||||
layer := make([]uint8, 256*256)
|
||||
mark := func(px, py, w, h int, kind uint8) {
|
||||
for r := 0; r < h; r++ {
|
||||
yy := py + r
|
||||
if yy < 0 || yy > 255 {
|
||||
continue
|
||||
}
|
||||
row := yy * 256
|
||||
for c := 0; c < w; c++ {
|
||||
xx := px + c
|
||||
if xx < 0 || xx > 255 {
|
||||
continue
|
||||
}
|
||||
layer[row+xx] = kind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直接遍历块表而不是全球块范围:低缩放级下范围循环会膨胀到数十亿次空查询。
|
||||
// GCJ 偏移最多约 700m(74 格),粗裁剪时两侧各放宽两块保证不漏。
|
||||
const margin = 2 * BlockCells
|
||||
lo, hi := float64(x*step-margin), float64((x+1)*step+margin)
|
||||
rlo, rhi := float64(y*step-margin), float64((y+1)*step+margin)
|
||||
shift := crs == "gcj02"
|
||||
|
||||
draw := func(pass uint8, gbx, gby int, cb, ib []byte) {
|
||||
bx, by := float64(gbx*BlockCells), float64(gby*BlockCells)
|
||||
if bx+BlockCells <= lo || bx >= hi || by+BlockCells <= rlo || by >= rhi {
|
||||
return
|
||||
}
|
||||
ddx, ddy := 0.0, 0.0
|
||||
if shift {
|
||||
ddx, ddy = blockShift(gbx, gby)
|
||||
}
|
||||
u0 := (bx + ddx - ox) * scale
|
||||
v0 := (by + ddy - oy) * scale
|
||||
|
||||
if scale >= 1 {
|
||||
w := int(math.Ceil(scale))
|
||||
for row := 0; row < BlockCells; row++ {
|
||||
base := row * 8
|
||||
for colB := 0; colB < 8; colB++ {
|
||||
var cbv, ibv byte
|
||||
if cb != nil {
|
||||
cbv = cb[base+colB]
|
||||
}
|
||||
if ib != nil {
|
||||
ibv = ib[base+colB]
|
||||
}
|
||||
if cbv|ibv == 0 {
|
||||
continue
|
||||
}
|
||||
py := int(math.Floor(v0 + float64(row)*scale))
|
||||
for bit := 0; bit < 8; bit++ {
|
||||
m := byte(0x80 >> bit)
|
||||
kind := cellKind(cbv&m != 0, ibv&m != 0)
|
||||
if kind != pass {
|
||||
continue
|
||||
}
|
||||
px := int(math.Floor(u0 + float64(colB*8+bit)*scale))
|
||||
mark(px, py, w, w, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 一格不足一像素:整块按多数类涂,三种混在一起的块以数量最多的为准
|
||||
if dominantKind(blockDiffCounts(cb, ib)) != pass {
|
||||
return
|
||||
}
|
||||
u1 := (bx + BlockCells + ddx - ox) * scale
|
||||
v1 := (by + BlockCells + ddy - oy) * scale
|
||||
px0, py0 := int(math.Floor(u0)), int(math.Floor(v0))
|
||||
px1, py1 := int(math.Ceil(u1)), int(math.Ceil(v1))
|
||||
if px1-px0 < 1 {
|
||||
px1 = px0 + 1
|
||||
}
|
||||
if py1-py0 < 1 {
|
||||
py1 = py0 + 1
|
||||
}
|
||||
mark(px0, py0, px1-px0, py1-py0, pass)
|
||||
}
|
||||
|
||||
for _, pass := range []uint8{diffBoth, diffGain, diffLost} {
|
||||
for key, cb := range cur {
|
||||
draw(pass, key[0], key[1], cb, inc[key])
|
||||
}
|
||||
// 只在快照里的块单独走一遍,两边都有的块不再重复画
|
||||
for key, ib := range inc {
|
||||
if _, dup := cur[key]; dup {
|
||||
continue
|
||||
}
|
||||
draw(pass, key[0], key[1], nil, ib)
|
||||
}
|
||||
}
|
||||
|
||||
img := image.NewNRGBA(image.Rect(0, 0, 256, 256))
|
||||
for i, kind := range layer {
|
||||
if kind == diffNone {
|
||||
continue
|
||||
}
|
||||
var c [4]byte
|
||||
switch kind {
|
||||
case diffBoth:
|
||||
c = DiffBothColor
|
||||
case diffGain:
|
||||
c = DiffGainColor
|
||||
default:
|
||||
c = DiffLostColor
|
||||
}
|
||||
o := i * 4
|
||||
img.Pix[o], img.Pix[o+1], img.Pix[o+2], img.Pix[o+3] = c[0], c[1], c[2], c[3]
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func cellKind(inCur, inInc bool) uint8 {
|
||||
switch {
|
||||
case inCur && inInc:
|
||||
return diffBoth
|
||||
case inInc:
|
||||
return diffGain
|
||||
case inCur:
|
||||
return diffLost
|
||||
}
|
||||
return diffNone
|
||||
}
|
||||
|
||||
// blockDiffCounts 数一个块里三类各占多少格。两个位图都可能缺失(只有一边有这个块)。
|
||||
func blockDiffCounts(cb, ib []byte) (both, gain, lost int) {
|
||||
n := len(cb)
|
||||
if len(ib) > n {
|
||||
n = len(ib)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
var c, in byte
|
||||
if i < len(cb) {
|
||||
c = cb[i]
|
||||
}
|
||||
if i < len(ib) {
|
||||
in = ib[i]
|
||||
}
|
||||
both += int(popcount[c&in])
|
||||
gain += int(popcount[in&^c])
|
||||
lost += int(popcount[c&^in])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// dominantKind 取三类里占得最多的一类,用来在粗缩放级下决定整块的着色。
|
||||
func dominantKind(both, gain, lost int) uint8 {
|
||||
switch {
|
||||
case gain > 0 && gain >= both && gain >= lost:
|
||||
return diffGain
|
||||
case lost > 0 && lost >= both:
|
||||
return diffLost
|
||||
case both > 0:
|
||||
return diffBoth
|
||||
}
|
||||
return diffNone
|
||||
}
|
||||
Reference in New Issue
Block a user