import type { GeoPoint, MapViewport } from '@/domain/poi' import { describe, expect, it } from 'vitest' import { getPoiRepository } from '@/data/poi' import { GUANGMING_POC_BOUNDS, isWithinGuangmingArea } from '@/domain/poi' import { computeClusterViewport, isSignificantViewportChange } from '@/services/map' const current: MapViewport = { longitude: 113.935, latitude: 22.748, scale: 13 } function shifted(delta: Partial): MapViewport { return { longitude: current.longitude + (delta.longitude ?? 0), latitude: current.latitude + (delta.latitude ?? 0), scale: current.scale + (delta.scale ?? 0), } } describe('isSignificantViewportChange', () => { it('丢弃地图回传中心点的量化噪声', () => { expect(isSignificantViewportChange(current, shifted({}))).toBe(false) expect(isSignificantViewportChange(current, shifted({ longitude: 1e-6 }))).toBe(false) expect(isSignificantViewportChange(current, shifted({ latitude: -1e-6 }))).toBe(false) expect(isSignificantViewportChange(current, shifted({ scale: 0.001 }))).toBe(false) }) it('接受真实拖动与缩放', () => { expect(isSignificantViewportChange(current, shifted({ longitude: 0.01 }))).toBe(true) expect(isSignificantViewportChange(current, shifted({ latitude: -0.01 }))).toBe(true) expect(isSignificantViewportChange(current, shifted({ scale: 1 }))).toBe(true) }) it('单个维度超阈值就算变化', () => { const next = shifted({ longitude: 1e-6, latitude: 1e-6, scale: 2 }) expect(isSignificantViewportChange(current, next)).toBe(true) }) }) const VIEWPORT_SIZE = { width: 375, height: 560 } describe('computeClusterViewport', () => { it('没有可用点位时返回 null', () => { expect(computeClusterViewport([])).toBeNull() // (0, 0) 是定位没拿到 fix 时的产物,不该参与聚类。 expect(computeClusterViewport([{ longitude: 0, latitude: 0 }])).toBeNull() expect(computeClusterViewport([{ longitude: Number.NaN, latitude: 22.7 }])).toBeNull() }) it('中心点取算术中心,密度大的一侧更靠中间', () => { // 三个点挤在西侧,一个点在东侧远处:算术中心应偏西,而不是落在东西正中。 const points: GeoPoint[] = [ { longitude: 113.90, latitude: 22.75 }, { longitude: 113.91, latitude: 22.75 }, { longitude: 113.92, latitude: 22.75 }, { longitude: 114.00, latitude: 22.75 }, ] const viewport = computeClusterViewport(points, VIEWPORT_SIZE)! expect(viewport.longitude).toBeCloseTo(113.9325, 4) // 外接矩形中心是 113.95,算术中心必须比它更靠西。 expect(viewport.longitude).toBeLessThan(113.95) }) it('跨度越大 scale 越小,且始终落在微信允许的 3~20', () => { const tight = computeClusterViewport([ { longitude: 113.93, latitude: 22.75 }, { longitude: 113.94, latitude: 22.76 }, ], VIEWPORT_SIZE)! const wide = computeClusterViewport([ { longitude: 113.50, latitude: 22.40 }, { longitude: 114.40, latitude: 23.10 }, ], VIEWPORT_SIZE)! expect(tight.scale).toBeGreaterThan(wide.scale) for (const scale of [tight.scale, wide.scale]) { expect(scale).toBeGreaterThanOrEqual(3) expect(scale).toBeLessThanOrEqual(20) } }) it('所有点位重合时给街区级视野', () => { const viewport = computeClusterViewport([ { longitude: 113.93, latitude: 22.75 }, { longitude: 113.93, latitude: 22.75 }, ], VIEWPORT_SIZE)! expect(viewport).toEqual({ longitude: 113.93, latitude: 22.75, scale: 15 }) }) it('屏幕越窄 scale 越小', () => { const points: GeoPoint[] = [ { longitude: 113.88, latitude: 22.70 }, { longitude: 113.97, latitude: 22.81 }, ] const narrow = computeClusterViewport(points, { width: 320, height: 480 })! const wide = computeClusterViewport(points, { width: 768, height: 900 })! expect(narrow.scale).toBeLessThanOrEqual(wide.scale) }) it('真实数据集的聚类视野落在光明区范围内,且装得下全部点位', () => { const pois = getPoiRepository().getPoiSummaries() const viewport = computeClusterViewport(pois, VIEWPORT_SIZE)! expect(viewport.longitude).toBeGreaterThanOrEqual(GUANGMING_POC_BOUNDS.minLongitude) expect(viewport.longitude).toBeLessThanOrEqual(GUANGMING_POC_BOUNDS.maxLongitude) expect(viewport.latitude).toBeGreaterThanOrEqual(GUANGMING_POC_BOUNDS.minLatitude) expect(viewport.latitude).toBeLessThanOrEqual(GUANGMING_POC_BOUNDS.maxLatitude) // 区级取景:11 级会把光明区缩成一小块,14 级又装不下南北 12.6 km 的跨度。 expect(viewport.scale).toBeGreaterThanOrEqual(12) expect(viewport.scale).toBeLessThan(14) }) }) describe('isWithinGuangmingArea', () => { it('本期全部点位都落在包络内', () => { for (const poi of getPoiRepository().getPoiSummaries()) expect(isWithinGuangmingArea(poi.longitude, poi.latitude)).toBe(true) }) it('区外坐标返回 false,用于决定开屏是否把视野挪到用户身上', () => { // 深圳市民中心、广州塔:都是真实可达的位置,但画面里看不到本期点位。 expect(isWithinGuangmingArea(114.0655, 22.5477)).toBe(false) expect(isWithinGuangmingArea(113.3245, 23.1066)).toBe(false) // (0, 0) 落在几内亚湾,同样在包络外。 expect(isWithinGuangmingArea(0, 0)).toBe(false) }) it('包络边界闭区间', () => { const { minLongitude, maxLongitude, minLatitude, maxLatitude } = GUANGMING_POC_BOUNDS expect(isWithinGuangmingArea(minLongitude, minLatitude)).toBe(true) expect(isWithinGuangmingArea(maxLongitude, maxLatitude)).toBe(true) expect(isWithinGuangmingArea(minLongitude - 0.001, minLatitude)).toBe(false) expect(isWithinGuangmingArea(maxLongitude, maxLatitude + 0.001)).toBe(false) }) })