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