189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
package fog
|
|
|
|
import (
|
|
"image"
|
|
"math"
|
|
|
|
"rond-with-you/internal/geo"
|
|
)
|
|
|
|
// 迷雾覆盖层配色:填充用偏亮的蓝,边界再描一圈更深的轮廓,
|
|
// 这样在浅色底图上也能一眼看出「已揭开」的范围。
|
|
var (
|
|
FogColor = [4]byte{48, 122, 224, 132}
|
|
FogEdge = [4]byte{16, 52, 132, 232}
|
|
)
|
|
|
|
// RenderTile 渲染一张 256x256 的迷雾透明瓦片。
|
|
//
|
|
// z/x/y 为 slippy 瓦片坐标。crs 指明底图坐标系:
|
|
// 迷雾网格是 WGS-84,而高德这类底图把 GCJ-02 坐标画在自己的名义位置上,
|
|
// 所以 gcj02 底图下每个块要先做 WGS84→GCJ02 正变换再投影;
|
|
// 块只有 600m 见方,偏移场在块内变化不足 1px,按块中心算一次即可。
|
|
// wgs84 底图(天地图/自备)则不偏移。
|
|
// masks 是被遮罩盖住、对访客不显示的区域(nil = 全部显示)。被遮住的格子既不画填充,
|
|
// 也不算作「未探索」的邻居——否则遮罩边缘会被描出一圈轮廓,等于把隐藏范围本身暴露了。
|
|
func RenderTile(blocks map[[2]int][]byte, z, x, y int, crs string, masks *MaskSet) *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)
|
|
// 第一遍只画掩码,第二遍再按邻域决定填充色还是轮廓色:
|
|
// 只有紧邻「未探索」的像素才描边,已探索区域内部保持平涂。
|
|
var mask [256 * 256]bool
|
|
// hole 记录「因为遮罩而不画」的像素,第二遍描边时把它当作正常像素跳过
|
|
var hole []bool
|
|
if !masks.Empty() {
|
|
hole = make([]bool, 256*256)
|
|
}
|
|
mark := func(px, py int) {
|
|
if px < 0 || px > 255 || py < 0 || py > 255 {
|
|
return
|
|
}
|
|
mask[py*256+px] = true
|
|
}
|
|
fill := func(px, py, w, h int) {
|
|
for r := 0; r < h; r++ {
|
|
for c := 0; c < w; c++ {
|
|
mark(px+c, py+r)
|
|
}
|
|
}
|
|
}
|
|
// markHole 把一段像素标成「被遮罩隐去」
|
|
markHole := func(px, py, w, h int) {
|
|
if hole == nil {
|
|
return
|
|
}
|
|
for r := 0; r < h; r++ {
|
|
for c := 0; c < w; c++ {
|
|
x, y := px+c, py+r
|
|
if x >= 0 && x <= 255 && y >= 0 && y <= 255 {
|
|
hole[y*256+x] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 直接遍历块表而不是全球块范围:低缩放级下范围循环会膨胀到数十亿次空查询。
|
|
// 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"
|
|
|
|
for key, bm := range blocks {
|
|
gbx, gby := key[0], key[1]
|
|
bx, by := float64(gbx*BlockCells), float64(gby*BlockCells)
|
|
if bx+BlockCells <= lo || bx >= hi || by+BlockCells <= rlo || by >= rhi {
|
|
continue
|
|
}
|
|
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++ {
|
|
line := bm[row*8 : row*8+8]
|
|
if line[0]|line[1]|line[2]|line[3]|line[4]|line[5]|line[6]|line[7] == 0 {
|
|
continue
|
|
}
|
|
py := int(math.Floor(v0 + float64(row)*scale))
|
|
for colB, byteVal := range line {
|
|
if byteVal == 0 {
|
|
continue
|
|
}
|
|
for bit := 0; bit < 8; bit++ {
|
|
if byteVal&(0x80>>bit) == 0 {
|
|
continue
|
|
}
|
|
px := int(math.Floor(u0 + float64(colB*8+bit)*scale))
|
|
if masks.Hit(gbx*BlockCells+colB*8+bit, gby*BlockCells+row) {
|
|
markHole(px, py, w, w)
|
|
continue
|
|
}
|
|
fill(px, py, w, w)
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// 格子比像素还小(低缩放级):整块只要有探索就整块涂上。
|
|
// 这时按块中心判遮罩——缩到这种级别,块本身还不到一个像素。
|
|
if popcountSum(bm) == 0 {
|
|
continue
|
|
}
|
|
if masks.Hit(gbx*BlockCells+BlockCells/2, gby*BlockCells+BlockCells/2) {
|
|
continue
|
|
}
|
|
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
|
|
}
|
|
fill(px0, py0, px1-px0, py1-py0)
|
|
}
|
|
|
|
// 第二遍着色。瓦片外的邻格按「已探索」处理,避免在瓦片接缝处
|
|
// 凭空描出一条网格线(真实边界由邻瓦片自己画)。
|
|
img := image.NewNRGBA(image.Rect(0, 0, 256, 256))
|
|
for py := 0; py < 256; py++ {
|
|
for px := 0; px < 256; px++ {
|
|
if !mask[py*256+px] {
|
|
continue
|
|
}
|
|
// 已探索的邻居:被遮罩隐去的也算「不算未探索」,免得把隐藏范围描出轮廓
|
|
lit := func(i int) bool { return mask[i] || (hole != nil && hole[i]) }
|
|
edge := false
|
|
if px > 0 && !lit(py*256+px-1) {
|
|
edge = true
|
|
} else if px < 255 && !lit(py*256+px+1) {
|
|
edge = true
|
|
} else if py > 0 && !lit((py-1)*256+px) {
|
|
edge = true
|
|
} else if py < 255 && !lit((py+1)*256+px) {
|
|
edge = true
|
|
}
|
|
c := FogColor
|
|
if edge {
|
|
c = FogEdge
|
|
}
|
|
i := (py*256 + px) * 4
|
|
img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = c[0], c[1], c[2], c[3]
|
|
}
|
|
}
|
|
return img
|
|
}
|
|
|
|
// blockShift 返回该块中心在 GCJ-02 底图下的像素偏移(单位:基网格格子)。
|
|
func blockShift(gbx, gby int) (float64, float64) {
|
|
lon, lat := cellLonLat(gbx*BlockCells+BlockCells/2, gby*BlockCells+BlockCells/2)
|
|
glat, glon := geo.WGS84ToGCJ02(lat, lon)
|
|
return mercX(glon) - mercX(lon), mercY(glat) - mercY(lat)
|
|
}
|
|
|
|
func mercX(lon float64) float64 { return (lon + 180.0) / 360.0 * WorldCells }
|
|
|
|
func mercY(lat float64) float64 {
|
|
rad := lat * math.Pi / 180
|
|
return (math.Pi - math.Asinh(math.Tan(rad))) / (2 * math.Pi) * WorldCells
|
|
}
|
|
|
|
func cellLonLat(cx, cy int) (float64, float64) {
|
|
lon := (float64(cx)+0.5)/WorldCells*360.0 - 180.0
|
|
n := 2 * math.Pi * (float64(cy) + 0.5) / WorldCells
|
|
lat := math.Atan(math.Sinh(math.Pi-n)) * 180.0 / math.Pi
|
|
return lon, lat
|
|
}
|