diff --git a/app/page.tsx b/app/page.tsx index 1f2aec4..836cb0f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -17,20 +17,8 @@ import { MapComponent } from '@/components/map-component'; import { ThemeToggle } from '@/components/theme-toggle'; import { Timeline } from '@/app/timeline'; import { cn } from '@/lib/utils'; -import { useTimeline } from '@/hooks/use-timeline'; -import { useEffect } from 'react' -import { useRadarTile } from '@/hooks/use-radartile' -import { gql, useSubscription } from '@apollo/client' -const SUBSCRIPTION_QUERY = gql` - subscription { - statusUpdates { - id - message - status - } - } -` + export default function Page() { @@ -38,16 +26,6 @@ export default function Page() { const now = new Date(); const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7天前 const endDate = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000); // 3天后 - const { setTime } = useTimeline() - const { imgBitmap, fetchRadarTile } = useRadarTile({}) - - const { data, loading, error } = useSubscription(SUBSCRIPTION_QUERY) - - useEffect(() => { - if (data) { - console.log(data.statusUpdates) - } - }, [data]) return ( @@ -61,7 +39,7 @@ export default function Page() {
- + { console.log('Selected date:', date); - setTime(date) }} />
diff --git a/components/map-component.tsx b/components/map-component.tsx index b7d4291..ea6c4b5 100644 --- a/components/map-component.tsx +++ b/components/map-component.tsx @@ -7,6 +7,8 @@ import { useMapLocation } from '@/hooks/use-map-location' import { getSubdivisionRecommendation, detectPerformanceLevel, RegionMeshPresets } from '@/lib/tile-mesh' import { createColorMap, ColorMapType, } from '@/lib/color-maps' import { Colorbar } from './colorbar' +import { useRadarTile } from '@/hooks/use-radartile' +import { format, formatInTimeZone } from 'date-fns-tz' interface MapComponentProps { style?: string @@ -19,16 +21,14 @@ interface MapComponentProps { export function MapComponent({ style = 'https://api.maptiler.com/maps/019817f1-82a8-7f37-901d-4bedf68b27fb/style.json?key=hj3fxRdwF9KjEsBq8sYI', - // center = [103.851959, 1.290270], - // zoom = 11 - imgBitmap: propImgBitmap, - colorMapType = 'heatmap', + colorMapType = 'meteorological', onColorMapChange }: MapComponentProps) { + + const { fetchRadarTile, imgBitmap } = useRadarTile(); const mapContainer = useRef(null) - const { setMap } = useMap() + const { setMap, mapRef, currentDatetime, isMapReady } = useMap() const { location } = useMapLocation() - const imgBitmap = propImgBitmap const texRef = useRef(null) const lutTexRef = useRef(null) const glRef = useRef(null) @@ -36,6 +36,13 @@ export function MapComponent({ const [isReady, setIsReady] = useState(false) const [currentColorMapType, setCurrentColorMapType] = useState(colorMapType) + useEffect(() => { + if (!isMapReady || !currentDatetime) return; + const utc_time_str = formatInTimeZone(currentDatetime, 'UTC', 'yyyyMMddHHmmss') + const new_url = `http://localhost:3050/api/v1/data?datetime=${utc_time_str}&area=cn` + fetchRadarTile(new_url) + }, [currentDatetime, isMapReady]) + useEffect(() => { if (!mapContainer.current) return @@ -99,8 +106,9 @@ export function MapComponent({ // 对于灰度图,RGB通道通常相同,取红色通道作为灰度值 float value = texColor.r * 3.4; - if (value == 0.0) { - discard; + if (value < 0.07) { + fragColor= vec4(1.0,1.0,1.0,0.2); + return; } // normalizedValue = clamp(normalizedValue, 0.0, 1.0); @@ -109,8 +117,9 @@ export function MapComponent({ vec4 lutColor = texture(u_lut, vec2(value, 0.5)); // 添加一些透明度,使低值区域更透明 // float alpha = smoothstep(0.0, 0.1, value); - float alpha = 1.0; + float alpha = 0.7; fragColor = vec4(lutColor.rgb, alpha); + // fragColor = vec4(1.0,1.0,1.0,0.2); }` console.log(vertexSource, fragmentSource) @@ -164,8 +173,8 @@ export function MapComponent({ } gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); @@ -461,6 +470,10 @@ export function MapComponent({ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.bindTexture(gl.TEXTURE_2D, null); + + // Redraw the map + mapRef.current?.triggerRepaint() + } }, [imgBitmap, isReady]) @@ -554,8 +567,8 @@ function createLutTexture(gl: WebGL2RenderingContext, colorMapType: ColorMapType lut ) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); diff --git a/hooks/use-map-location.ts b/hooks/use-map-location.ts index 2f6e69f..f015d65 100644 --- a/hooks/use-map-location.ts +++ b/hooks/use-map-location.ts @@ -24,7 +24,7 @@ const LOCATIONS = { export type LocationKey = keyof typeof LOCATIONS export function useMapLocation() { - const [currentLocation, setCurrentLocation] = useState('usa') + const [currentLocation, setCurrentLocation] = useState('china') const { flyTo, isMapReady } = useMap() const flyToLocation = useCallback((location: LocationKey) => { diff --git a/lib/color-maps.ts b/lib/color-maps.ts index 7b74154..1c0a2b0 100644 --- a/lib/color-maps.ts +++ b/lib/color-maps.ts @@ -1,5 +1,5 @@ // 色标函数集合 -export type ColorMapType = 'radar' | 'rainbow' | 'heatmap' | 'viridis' | 'plasma' | 'grayscale'; +export type ColorMapType = 'radar' | 'rainbow' | 'heatmap' | 'viridis' | 'plasma' | 'grayscale' | 'meteorological'; // 雷达色标 (深蓝到红色,类似气象雷达) export function createRadarColorMap(): Uint8Array { @@ -207,11 +207,109 @@ export function createColorMap(type: ColorMapType): Uint8Array { return createPlasmaColorMap(); case 'grayscale': return createGrayscaleColorMap(); + case 'meteorological': + return createMeteorologicalColorMap(); default: return createRadarColorMap(); } } +// COLOR_MAP = [ +// ("#01a0f6", (5, 10)), +// ("#00ecec", (10, 15)), +// ("#6dfa3d", (15, 20)), +// ("#00d802", (20, 25)), +// ("#019001", (25, 30)), +// ("#ffff04", (30, 35)), +// ("#e7c002", (35, 40)), +// ("#ff9002", (40, 45)), +// ("#ff0201", (45, 50)), +// ("#d60101", (50, 55)), +// ("#c00100", (55, 60)), +// ("#ff00f0", (60, 65)), +// ("#9600b4", (65, 70)), +// ("#ad90f0", (70, 75)), +// ] + + + +// 根据具体数值区间创建的气象雷达色标 +export function createMeteorologicalColorMap(): Uint8Array { + const lut = new Uint8Array(256 * 4); + + // 定义颜色和数值区间对应关系 + const colorRanges: Array<{ color: [number, number, number]; range: [number, number] }> = [ + { color: [1, 160, 246], range: [5, 10] }, // #01a0f6 + { color: [0, 236, 236], range: [10, 15] }, // #00ecec + { color: [109, 250, 61], range: [15, 20] }, // #6dfa3d + { color: [0, 216, 2], range: [20, 25] }, // #00d802 + { color: [1, 144, 1], range: [25, 30] }, // #019001 + { color: [255, 255, 4], range: [30, 35] }, // #ffff04 (黄色,重要节点) + { color: [231, 192, 2], range: [35, 40] }, // #e7c002 + { color: [255, 144, 2], range: [40, 45] }, // #ff9002 + { color: [255, 2, 1], range: [45, 50] }, // #ff0201 + { color: [214, 1, 1], range: [50, 55] }, // #d60101 + { color: [192, 1, 0], range: [55, 60] }, // #c00100 + { color: [255, 0, 240], range: [60, 65] }, // #ff00f0 (紫色,重要节点) + { color: [150, 0, 180], range: [65, 70] }, // #9600b4 + { color: [173, 144, 240], range: [70, 75] } // #ad90f0 + ]; + + for (let i = 0; i < 256; i++) { + // 将0-255映射到0-75的数值范围 + const value = (i / 255.0) * 75; + + let r = 0, g = 0, b = 0; + + // 找到对应的颜色区间 + let found = false; + for (let j = 0; j < colorRanges.length; j++) { + const range = colorRanges[j]; + if (value >= range.range[0] && value <= range.range[1]) { + // 在区间内进行线性插值 + const localT = (value - range.range[0]) / (range.range[1] - range.range[0]); + + if (j < colorRanges.length - 1) { + const nextRange = colorRanges[j + 1]; + // 与下一个颜色进行插值 + r = Math.floor(range.color[0] + localT * (nextRange.color[0] - range.color[0])); + g = Math.floor(range.color[1] + localT * (nextRange.color[1] - range.color[1])); + b = Math.floor(range.color[2] + localT * (nextRange.color[2] - range.color[2])); + } else { + // 最后一个区间,使用固定颜色 + r = range.color[0]; + g = range.color[1]; + b = range.color[2]; + } + found = true; + break; + } + } + + // 如果没有找到对应区间,使用最近的颜色 + if (!found) { + if (value < 5) { + const firstRange = colorRanges[0]!; + r = firstRange.color[0]; + g = firstRange.color[1]; + b = firstRange.color[2]; + } else { + const lastRange = colorRanges[colorRanges.length - 1]!; + r = lastRange.color[0]; + g = lastRange.color[1]; + b = lastRange.color[2]; + } + } + + lut[i * 4] = r; + lut[i * 4 + 1] = g; + lut[i * 4 + 2] = b; + lut[i * 4 + 3] = 255; + } + + return lut; +} + // 获取所有可用的色标类型 export function getAvailableColorMaps(): { value: ColorMapType; label: string }[] { return [ @@ -220,6 +318,7 @@ export function getAvailableColorMaps(): { value: ColorMapType; label: string }[ { value: 'heatmap', label: '热力图色标' }, { value: 'viridis', label: 'Viridis色标' }, { value: 'plasma', label: 'Plasma色标' }, - { value: 'grayscale', label: '灰度色标' } + { value: 'grayscale', label: '灰度色标' }, + { value: 'meteorological', label: '气象雷达色标' } ]; } \ No newline at end of file