render real data

This commit is contained in:
Tsuki 2025-08-07 21:42:04 +08:00
parent 448f71a07c
commit 06ba5d7ab1
4 changed files with 130 additions and 41 deletions

View File

@ -17,20 +17,8 @@ import { MapComponent } from '@/components/map-component';
import { ThemeToggle } from '@/components/theme-toggle'; import { ThemeToggle } from '@/components/theme-toggle';
import { Timeline } from '@/app/timeline'; import { Timeline } from '@/app/timeline';
import { cn } from '@/lib/utils'; 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() { export default function Page() {
@ -38,16 +26,6 @@ export default function Page() {
const now = new Date(); const now = new Date();
const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7天前 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 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 ( return (
<SidebarProvider> <SidebarProvider>
@ -61,7 +39,7 @@ export default function Page() {
</div> </div>
</header> </header>
<div className="relative h-full w-full flex flex-col"> <div className="relative h-full w-full flex flex-col">
<MapComponent imgBitmap={imgBitmap} /> <MapComponent />
<Timeline <Timeline
className={ className={
cn( cn(
@ -74,7 +52,6 @@ export default function Page() {
endDate={endDate} endDate={endDate}
onDateChange={(date) => { onDateChange={(date) => {
console.log('Selected date:', date); console.log('Selected date:', date);
setTime(date)
}} }}
/> />
</div> </div>

View File

@ -7,6 +7,8 @@ import { useMapLocation } from '@/hooks/use-map-location'
import { getSubdivisionRecommendation, detectPerformanceLevel, RegionMeshPresets } from '@/lib/tile-mesh' import { getSubdivisionRecommendation, detectPerformanceLevel, RegionMeshPresets } from '@/lib/tile-mesh'
import { createColorMap, ColorMapType, } from '@/lib/color-maps' import { createColorMap, ColorMapType, } from '@/lib/color-maps'
import { Colorbar } from './colorbar' import { Colorbar } from './colorbar'
import { useRadarTile } from '@/hooks/use-radartile'
import { format, formatInTimeZone } from 'date-fns-tz'
interface MapComponentProps { interface MapComponentProps {
style?: string style?: string
@ -19,16 +21,14 @@ interface MapComponentProps {
export function MapComponent({ export function MapComponent({
style = 'https://api.maptiler.com/maps/019817f1-82a8-7f37-901d-4bedf68b27fb/style.json?key=hj3fxRdwF9KjEsBq8sYI', style = 'https://api.maptiler.com/maps/019817f1-82a8-7f37-901d-4bedf68b27fb/style.json?key=hj3fxRdwF9KjEsBq8sYI',
// center = [103.851959, 1.290270], colorMapType = 'meteorological',
// zoom = 11
imgBitmap: propImgBitmap,
colorMapType = 'heatmap',
onColorMapChange onColorMapChange
}: MapComponentProps) { }: MapComponentProps) {
const { fetchRadarTile, imgBitmap } = useRadarTile();
const mapContainer = useRef<HTMLDivElement>(null) const mapContainer = useRef<HTMLDivElement>(null)
const { setMap } = useMap() const { setMap, mapRef, currentDatetime, isMapReady } = useMap()
const { location } = useMapLocation() const { location } = useMapLocation()
const imgBitmap = propImgBitmap
const texRef = useRef<WebGLTexture | null>(null) const texRef = useRef<WebGLTexture | null>(null)
const lutTexRef = useRef<WebGLTexture | null>(null) const lutTexRef = useRef<WebGLTexture | null>(null)
const glRef = useRef<WebGL2RenderingContext | null>(null) const glRef = useRef<WebGL2RenderingContext | null>(null)
@ -36,6 +36,13 @@ export function MapComponent({
const [isReady, setIsReady] = useState<boolean>(false) const [isReady, setIsReady] = useState<boolean>(false)
const [currentColorMapType, setCurrentColorMapType] = useState<ColorMapType>(colorMapType) const [currentColorMapType, setCurrentColorMapType] = useState<ColorMapType>(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(() => { useEffect(() => {
if (!mapContainer.current) return if (!mapContainer.current) return
@ -99,8 +106,9 @@ export function MapComponent({
// 对于灰度图RGB通道通常相同取红色通道作为灰度值 // 对于灰度图RGB通道通常相同取红色通道作为灰度值
float value = texColor.r * 3.4; float value = texColor.r * 3.4;
if (value == 0.0) { if (value < 0.07) {
discard; fragColor= vec4(1.0,1.0,1.0,0.2);
return;
} }
// normalizedValue = clamp(normalizedValue, 0.0, 1.0); // normalizedValue = clamp(normalizedValue, 0.0, 1.0);
@ -109,8 +117,9 @@ export function MapComponent({
vec4 lutColor = texture(u_lut, vec2(value, 0.5)); vec4 lutColor = texture(u_lut, vec2(value, 0.5));
// 添加一些透明度,使低值区域更透明 // 添加一些透明度,使低值区域更透明
// float alpha = smoothstep(0.0, 0.1, value); // float alpha = smoothstep(0.0, 0.1, value);
float alpha = 1.0; float alpha = 0.7;
fragColor = vec4(lutColor.rgb, alpha); fragColor = vec4(lutColor.rgb, alpha);
// fragColor = vec4(1.0,1.0,1.0,0.2);
}` }`
console.log(vertexSource, fragmentSource) console.log(vertexSource, fragmentSource)
@ -164,8 +173,8 @@ export function MapComponent({
} }
gl.bindTexture(gl.TEXTURE_2D, tex); gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 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_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
// Redraw the map
mapRef.current?.triggerRepaint()
} }
}, [imgBitmap, isReady]) }, [imgBitmap, isReady])
@ -554,8 +567,8 @@ function createLutTexture(gl: WebGL2RenderingContext, colorMapType: ColorMapType
lut lut
) )
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 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_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

View File

@ -24,7 +24,7 @@ const LOCATIONS = {
export type LocationKey = keyof typeof LOCATIONS export type LocationKey = keyof typeof LOCATIONS
export function useMapLocation() { export function useMapLocation() {
const [currentLocation, setCurrentLocation] = useState<LocationKey>('usa') const [currentLocation, setCurrentLocation] = useState<LocationKey>('china')
const { flyTo, isMapReady } = useMap() const { flyTo, isMapReady } = useMap()
const flyToLocation = useCallback((location: LocationKey) => { const flyToLocation = useCallback((location: LocationKey) => {

View File

@ -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 { export function createRadarColorMap(): Uint8Array {
@ -207,11 +207,109 @@ export function createColorMap(type: ColorMapType): Uint8Array {
return createPlasmaColorMap(); return createPlasmaColorMap();
case 'grayscale': case 'grayscale':
return createGrayscaleColorMap(); return createGrayscaleColorMap();
case 'meteorological':
return createMeteorologicalColorMap();
default: default:
return createRadarColorMap(); 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 }[] { export function getAvailableColorMaps(): { value: ColorMapType; label: string }[] {
return [ return [
@ -220,6 +318,7 @@ export function getAvailableColorMaps(): { value: ColorMapType; label: string }[
{ value: 'heatmap', label: '热力图色标' }, { value: 'heatmap', label: '热力图色标' },
{ value: 'viridis', label: 'Viridis色标' }, { value: 'viridis', label: 'Viridis色标' },
{ value: 'plasma', label: 'Plasma色标' }, { value: 'plasma', label: 'Plasma色标' },
{ value: 'grayscale', label: '灰度色标' } { value: 'grayscale', label: '灰度色标' },
{ value: 'meteorological', label: '气象雷达色标' }
]; ];
} }