import { useState, useCallback, useRef, useEffect } from 'react' import { useImageFrameLoader } from './use-image-frame-loader' interface UseRadarTileOptions { maxBufferSize?: number; targetPrefetch?: number; preCacheCount?: number; // 前后各预缓存N个帧 generateUrl?: (timestamp: number) => string; // 根据时间戳生成URL的函数 } interface RadarTileFrame { timestamp: number; frameIndex: number; url: string; } export function useRadarTile({ maxBufferSize = 12, targetPrefetch = 4, preCacheCount = 3, generateUrl }: UseRadarTileOptions = {}) { const [currentFrame, setCurrentFrame] = useState(null) const [currentBitmap, setCurrentBitmap] = useState(null) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) // 时间戳到帧索引的映射缓存 const timestampToFrameMap = useRef>(new Map()) // 订阅取消函数 const unsubscribeRef = useRef<(() => void) | null>(null) const { loadFrame, clearCache, getFrame, isFrameCached, getCacheStats, subscribeToFrame, isLoading: frameLoaderIsLoading, error: frameLoaderError, isWorkerSupported } = useImageFrameLoader({ maxBufferSize, targetPrefetch }) // 生成稳定的帧索引(基于时间戳) const getFrameIndex = useCallback((timestamp: number): number => { if (timestampToFrameMap.current.has(timestamp)) { return timestampToFrameMap.current.get(timestamp)! } const frameIndex = Math.abs(timestamp.toString().split('').reduce((a, b) => { a = ((a << 5) - a) + parseInt(b) || 0 return a & a }, 0)) timestampToFrameMap.current.set(timestamp, frameIndex) return frameIndex }, []) // 基础功能:切换到指定时间的帧 const switchToTime = useCallback(async (timestamp: number) => { if (!generateUrl) { setError('generateUrl function is required') return null } const url = generateUrl(timestamp) const frameIndex = getFrameIndex(timestamp) const frame: RadarTileFrame = { timestamp, frameIndex, url } setCurrentFrame(frame) setError(null) setIsLoading(true) // 检查是否已缓存 const cachedBitmap = getFrame(frameIndex) if (cachedBitmap) { setCurrentBitmap(cachedBitmap) setIsLoading(false) return cachedBitmap } try { // 清理之前的订阅 if (unsubscribeRef.current) { unsubscribeRef.current() } // 订阅帧加载完成通知 unsubscribeRef.current = subscribeToFrame(frameIndex, (loadedFrameIndex, imageBitmap) => { if (loadedFrameIndex === frameIndex) { setCurrentBitmap(imageBitmap) setIsLoading(false) unsubscribeRef.current = null } }) // 使用高优先级加载当前帧 loadFrame(url, frameIndex, 'high') // 触发预缓存 await preCacheAroundTime(timestamp) return null // 实际的bitmap会通过订阅通知获取 } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Unknown error' setError(errorMessage) setIsLoading(false) if (unsubscribeRef.current) { unsubscribeRef.current() unsubscribeRef.current = null } return null } }, [generateUrl, getFrameIndex, getFrame, loadFrame, subscribeToFrame]) // 在指定时间前后预缓存N个帧 const preCacheAroundTime = useCallback(async (centerTimestamp: number, timeInterval: number = 360000) => { // 默认5分钟间隔 if (!generateUrl) return const urlsWithFrameIndices: Array<{url: string, frameIndex: number}> = [] // 生成前后各N个时间点的URL和对应的frameIndex for (let i = -preCacheCount; i <= preCacheCount; i++) { if (i === 0) continue // 跳过当前帧 const timestamp = centerTimestamp + (i * timeInterval) const url = generateUrl(timestamp) const frameIndex = getFrameIndex(timestamp) urlsWithFrameIndices.push({ url, frameIndex }) } // 分别加载每个帧,使用正确的frameIndex if (urlsWithFrameIndices.length > 0) { urlsWithFrameIndices.forEach(({ url, frameIndex }) => { loadFrame(url, frameIndex, 'normal') }) } }, [generateUrl, getFrameIndex, preCacheCount, loadFrame]) // 清理订阅当组件卸载时 useEffect(() => { return () => { if (unsubscribeRef.current) { unsubscribeRef.current() } } }, []) // 同步错误和加载状态 useEffect(() => { if (frameLoaderError && !error) { setError(frameLoaderError) setIsLoading(false) } }, [frameLoaderError, error]) useEffect(() => { setIsLoading(frameLoaderIsLoading) }, [frameLoaderIsLoading]) // 高级功能:清空缓存 const clearAllCache = useCallback(() => { if (unsubscribeRef.current) { unsubscribeRef.current() unsubscribeRef.current = null } clearCache() timestampToFrameMap.current.clear() setCurrentBitmap(null) setCurrentFrame(null) setError(null) setIsLoading(false) }, [clearCache]) // 高级功能:预缓存指定时间范围 const preCacheTimeRange = useCallback(async (startTime: number, endTime: number, timeInterval: number = 300000) => { if (!generateUrl) return const urlsWithFrameIndices: Array<{url: string, frameIndex: number}> = [] for (let timestamp = startTime; timestamp <= endTime; timestamp += timeInterval) { const url = generateUrl(timestamp) const frameIndex = getFrameIndex(timestamp) urlsWithFrameIndices.push({ url, frameIndex }) } // 分别加载每个帧,使用正确的frameIndex if (urlsWithFrameIndices.length > 0) { urlsWithFrameIndices.forEach(({ url, frameIndex }) => { loadFrame(url, frameIndex, 'normal') }) } }, [generateUrl, getFrameIndex, loadFrame]) // 高级功能:检查时间是否已缓存 const isTimeCached = useCallback((timestamp: number): boolean => { const frameIndex = getFrameIndex(timestamp) return isFrameCached(frameIndex) }, [getFrameIndex, isFrameCached]) // 高级功能:获取指定时间的帧(不触发加载) const getFrameAtTime = useCallback((timestamp: number): ImageBitmap | null => { const frameIndex = getFrameIndex(timestamp) return getFrame(frameIndex) }, [getFrameIndex, getFrame]) return { // 核心功能:基于时间切换帧 switchToTime, // 当前状态 currentFrame, currentBitmap, currentTimestamp: currentFrame?.timestamp || null, // 加载状态 isLoading, error, // 高级功能 preCacheAroundTime, preCacheTimeRange, clearAllCache, getCacheStats, // 实用工具 isTimeCached, getFrameAtTime, getFrameIndex, // 底层信息 isWorkerSupported } }