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 }