144 lines
4.4 KiB
Go
144 lines
4.4 KiB
Go
package fog
|
||
|
||
import (
|
||
"math"
|
||
"testing"
|
||
)
|
||
|
||
// allOnesBlock 造一块位图全置位的块(每格都已探索)。
|
||
func allOnesBlock(gx, gy int) Block {
|
||
bm := make([]byte, BlockCells*BlockCells/8)
|
||
for i := range bm {
|
||
bm[i] = 0xFF
|
||
}
|
||
return Block{GX: gx, GY: gy, Bitmap: bm}
|
||
}
|
||
|
||
// blocksAround 造出覆盖这些遮罩包围盒的全部块。
|
||
func blocksAround(masks ...Mask) []Block {
|
||
var out []Block
|
||
seen := map[[2]int]bool{}
|
||
for _, m := range masks {
|
||
g := newMaskGrid(m)
|
||
for bx := g.x0 / BlockCells; bx <= g.x1/BlockCells; bx++ {
|
||
for by := g.y0 / BlockCells; by <= g.y1/BlockCells; by++ {
|
||
if seen[[2]int{bx, by}] {
|
||
continue
|
||
}
|
||
seen[[2]int{bx, by}] = true
|
||
out = append(out, allOnesBlock(bx, by))
|
||
}
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// TestMaskedCellsUnion 遮罩重叠时,被遮住的格子只能算一次。
|
||
// 曾经的实现是「按遮罩逐个累加命中格数」,重叠区域会被统计两三遍,
|
||
// 于是覆盖率里扣掉的格数偏大、与面积(另一套逐格算法)对不上。
|
||
func TestMaskedCellsUnion(t *testing.T) {
|
||
a := Mask{ID: 1, Lat: 24.70, Lon: 108.03, RadiusM: 2000}
|
||
b := Mask{ID: 2, Lat: 24.705, Lon: 108.035, RadiusM: 2000}
|
||
|
||
onlyA, onlyB := NewMaskSet([]Mask{a}), NewMaskSet([]Mask{b})
|
||
both := NewMaskSet([]Mask{a, b})
|
||
if onlyA == nil || onlyB == nil || both == nil {
|
||
t.Fatal("遮罩集合不应为空")
|
||
}
|
||
|
||
blocks := blocksAround(a, b)
|
||
var want int
|
||
for _, blk := range blocks {
|
||
for row := 0; row < BlockCells; row++ {
|
||
for col := 0; col < BlockCells; col++ {
|
||
if both.Hit(blk.GX*BlockCells+col, blk.GY*BlockCells+row) {
|
||
want++
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 用例前提:两块遮罩真的有重叠,否则测不出重复计数
|
||
if sum := countMasked(blocks, onlyA) + countMasked(blocks, onlyB); sum <= want {
|
||
t.Fatalf("两块遮罩没有重叠(单独 %d + %d,并集 %d),用例无效", sum, want, want)
|
||
}
|
||
|
||
if got := countMasked(blocks, both); got != want {
|
||
t.Errorf("重叠遮罩下 MaskedCells = %d,应为并集 %d(偏大即重复计数)", got, want)
|
||
}
|
||
}
|
||
|
||
// TestMaskedAreaMatchesCells 面积与格数必须出自同一个集合:
|
||
// 扣掉的面积换算成格数后要与统计出的格数相等。
|
||
func TestMaskedAreaMatchesCells(t *testing.T) {
|
||
a := Mask{ID: 1, Lat: 24.70, Lon: 108.03, RadiusM: 2000}
|
||
b := Mask{ID: 2, Lat: 24.705, Lon: 108.035, RadiusM: 2000}
|
||
set := NewMaskSet([]Mask{a, b})
|
||
blocks := blocksAround(a, b)
|
||
|
||
// 逐块按块中心纬度算格子面积(与 AreaKm2Excluding 内同一套算法)
|
||
var lostM2 float64
|
||
for _, blk := range blocks {
|
||
_, lat := cellLonLat(blk.GX*BlockCells+BlockCells/2, blk.GY*BlockCells+BlockCells/2)
|
||
cell := CellSideM * math.Cos(lat*math.Pi/180) * CellSideM
|
||
n := 0
|
||
for row := 0; row < BlockCells; row++ {
|
||
for col := 0; col < BlockCells; col++ {
|
||
if set.Hit(blk.GX*BlockCells+col, blk.GY*BlockCells+row) {
|
||
n++
|
||
}
|
||
}
|
||
}
|
||
lostM2 += float64(n) * cell
|
||
}
|
||
|
||
byKey := map[[2]int][]byte{}
|
||
for _, blk := range blocks {
|
||
byKey[[2]int{blk.GX, blk.GY}] = blk.Bitmap
|
||
}
|
||
gotCells, gotM2 := maskedStats(byKey, set)
|
||
if gotCells != countMasked(blocks, set) {
|
||
t.Errorf("遮罩格数 %d,暴力统计 %d", gotCells, countMasked(blocks, set))
|
||
}
|
||
if diff := math.Abs(gotM2 - lostM2); diff > 1e-9 {
|
||
t.Errorf("遮罩面积 %.6f m²,按格数算应为 %.6f m²(差 %.3f)", gotM2, lostM2, diff)
|
||
}
|
||
}
|
||
|
||
// countMasked 暴力数一遍被遮住的格子(测试基准)。
|
||
func countMasked(blocks []Block, set *MaskSet) int {
|
||
n := 0
|
||
for _, blk := range blocks {
|
||
for row := 0; row < BlockCells; row++ {
|
||
for col := 0; col < BlockCells; col++ {
|
||
if set.Hit(blk.GX*BlockCells+col, blk.GY*BlockCells+row) {
|
||
n++
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
// TestMaskFingerprint 指纹要能反映遮罩的任何改动,否则访客的瓦片缓存不会失效。
|
||
func TestMaskFingerprint(t *testing.T) {
|
||
a := Mask{ID: 1, Lat: 24.70, Lon: 108.03, RadiusM: 2000}
|
||
b := Mask{ID: 2, Lat: 24.705, Lon: 108.035, RadiusM: 1500}
|
||
|
||
if MaskFingerprint(nil) != "0" {
|
||
t.Error("空遮罩的指纹应为 0")
|
||
}
|
||
base := MaskFingerprint([]Mask{a})
|
||
cases := map[string][]Mask{
|
||
"多了一块": {a, b},
|
||
"半径改了": {{ID: 1, Lat: a.Lat, Lon: a.Lon, RadiusM: 3000}},
|
||
"位置挪了": {{ID: 1, Lat: a.Lat + 0.001, Lon: a.Lon, RadiusM: a.RadiusM}},
|
||
"换成另一块": {b},
|
||
}
|
||
for name, list := range cases {
|
||
if MaskFingerprint(list) == base {
|
||
t.Errorf("%s 之后指纹没变,访客缓存不会失效", name)
|
||
}
|
||
}
|
||
}
|