From 8d456dacf7bba66621f0f83242677afaff6a80da Mon Sep 17 00:00:00 2001 From: tsuki Date: Sat, 9 Aug 2025 22:24:11 +0800 Subject: [PATCH] add title --- app/map-context.tsx | 5 +++ app/page.tsx | 16 +++++--- app/tl.tsx | 52 ++++++++++++++++++++------ components/map-component.tsx | 71 +++++++++++++++++++++++++----------- hooks/use-timeline.ts | 4 +- 5 files changed, 108 insertions(+), 40 deletions(-) diff --git a/app/map-context.tsx b/app/map-context.tsx index 0853790..8b2608e 100644 --- a/app/map-context.tsx +++ b/app/map-context.tsx @@ -9,6 +9,7 @@ interface MapContextType { layers: React.RefObject mapState: MapState currentDatetime: Date | null + timelineDatetime: Date | null setMap: (map: Map, layers: any[]) => void flyTo: (options: { center: [number, number]; zoom: number; duration?: number }) => void zoomIn: () => void @@ -17,6 +18,7 @@ interface MapContextType { reset: () => void clearMap: () => void setTime: (date: Date) => void + setTimelineTime: (date: Date) => void isMapReady: boolean } @@ -44,6 +46,7 @@ export function MapProvider({ children }: MapProviderProps) { const layersRef = useRef([]) const [currentDatetime, setCurrentDatetime] = useState(null) + const [timelineDatetime, setTimelineDatetime] = useState(null) const setMap = (map: Map, layers: any[]) => { // 如果已经有地图实例,先清理旧的 @@ -131,6 +134,8 @@ export function MapProvider({ children }: MapProviderProps) { } const value: MapContextType = { + timelineDatetime, + setTimelineTime: setTimelineDatetime, setTime, currentDatetime, mapRef, diff --git a/app/page.tsx b/app/page.tsx index 721bf0e..74d7a94 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -18,15 +18,15 @@ import { ThemeToggle } from '@/components/theme-toggle'; // import { Timeline } from '@/app/timeline'; import { Timeline } from '@/app/tl'; import { cn } from '@/lib/utils'; +import { useMap } from './map-context' +import { format } from 'date-fns' +import { Label } from '@/components/ui/label' 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 { currentDatetime, timelineDatetime } = useMap() return ( @@ -36,7 +36,13 @@ export default function Page() { {/* */} {/* */}
- {/* */} + { + currentDatetime && ( + + ) + }
diff --git a/app/tl.tsx b/app/tl.tsx index 815bf3f..8462cf9 100644 --- a/app/tl.tsx +++ b/app/tl.tsx @@ -3,7 +3,7 @@ import vsSource from './glsl/timeline/vert.glsl'; import fsSource from './glsl/timeline/frag.glsl'; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; -import { ChevronLeft, ChevronRight, HomeIcon, LockIcon, Pause, Play, UnlockIcon } from "lucide-react"; +import { ChevronLeft, ChevronRight, HomeIcon, LockIcon, Pause, Play, RefreshCwIcon, UnlockIcon } from "lucide-react"; import { formatInTimeZone } from "date-fns-tz"; import { parse } from "date-fns" @@ -79,7 +79,7 @@ export const Timeline: React.FC = React.memo(({ timelineConfig, ...props }) => { - const { isPlaying, togglePlay, currentDatetime, setTime } = useTimeline({}) + const { isPlaying, togglePlay, currentDatetime, setTime, setTimelineTime, timelineDatetime } = useTimeline({}) const [lock, setLock] = useState(false) const canvasRef = useRef(null); const ticksCanvasRef = useRef(null); @@ -101,12 +101,18 @@ export const Timeline: React.FC = React.memo(({ if (!lock && data.statusUpdates.newestDt) { const newDt = parse(data.statusUpdates.newestDt + 'Z', 'yyyyMMddHHmmssX', new Date()) setTime(newDt) + setTimelineTime(newDt) + + if (timelineEngineRef.current) { + timelineEngineRef.current.replaceTimeMarkByTimestamp(newDt.getTime()) + } } } else { } } }, [data, lock]) + // 定时器效果 - 当播放时每隔指定时间执行操作 useEffect(() => { let intervalId: NodeJS.Timeout | null = null; @@ -127,12 +133,6 @@ export const Timeline: React.FC = React.memo(({ }; }, [isPlaying, timeStep]); - useEffect(() => { - if (currentDatetime && !lock) { - timelineEngineRef.current?.replaceTimeMarkByTimestamp(currentDatetime.getTime()) - } - }, [currentDatetime, lock]) - useEffect(() => { if (!ticksCanvasRef.current) return; @@ -171,11 +171,11 @@ export const Timeline: React.FC = React.memo(({ const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss') const response = await fetch(`http://localhost:3050/api/v1/data/nearest?datetime=${datestr}&area=cn`) + setTimelineTime(date) if (response.ok) { const data = await response.json() const nearestDatetime = data.nearest_data_time const nearestDate = new Date(Date.parse(nearestDatetime)) - setTime(nearestDate) } else { console.error('Failed to fetch data:', response.status) @@ -242,6 +242,26 @@ export const Timeline: React.FC = React.memo(({ } }, [timeStep]); + const handleRefresh = useCallback(async () => { + if (timelineEngineRef.current) { + + const date = new Date() + const datestr = formatInTimeZone(date, 'UTC', 'yyyyMMddHHmmss') + const response = await fetch(`http://localhost:3050/api/v1/data/nearest?datetime=${datestr}&area=cn`) + if (response.ok) { + const data = await response.json() + const nearestDatetime = data.nearest_data_time + const nearestDate = new Date(Date.parse(nearestDatetime)) + setTime(nearestDate) + timelineEngineRef.current.replaceTimeMarkByTimestamp(nearestDate.getTime()) + timelineEngineRef.current.ensureMarkInView() + } else { + console.error('Failed to fetch data:', response.status) + } + + } + }, []); + return (
@@ -301,10 +321,8 @@ export const Timeline: React.FC = React.memo(({ style={{ fontSize: '10px' }} > - - @@ -322,7 +340,17 @@ export const Timeline: React.FC = React.memo(({ {lock ? : } - +
diff --git a/components/map-component.tsx b/components/map-component.tsx index b9c7866..16055f4 100644 --- a/components/map-component.tsx +++ b/components/map-component.tsx @@ -83,49 +83,76 @@ export function MapComponent({ } const vertexSource = `#version 300 es - layout(location = 0) in vec2 a_pos; layout(location = 1) in vec2 a_tex_coord; ${shaderData.vertexShaderPrelude} ${shaderData.define} - out vec2 v_tex_coord; - + out vec2 v_merc; // 归一化墨卡托坐标 (0..1) + void main() { gl_Position = projectTile(a_pos); - v_tex_coord = a_tex_coord; - }`; + + // 用 MapLibre 注入的 u_projection_tile_mercator_coords 把 tile 坐标变为 mercator 归一坐标 + // merc = offset.xy + scale.zw * a_pos + v_merc = u_projection_tile_mercator_coords.xy + + u_projection_tile_mercator_coords.zw * a_pos; + } + ` + + // WebGL2 fragment shader const fragmentSource = `#version 300 es precision highp float; + uniform sampler2D u_tex; uniform sampler2D u_lut; + in vec2 v_merc; // 来自 VS 的归一化墨卡托 out vec4 fragColor; - in vec2 v_tex_coord; - - void main() { - vec4 texColor = texture(u_tex, v_tex_coord); - // 对于灰度图,RGB通道通常相同,取红色通道作为灰度值 - float value = texColor.r * 3.4; - if (value < 0.07) { - fragColor= vec4(1.0,1.0,1.0,0.2); - return; + const float PI = 3.141592653589793; + // 假设 bound = vec4(west, south, east, north) in degrees + const vec4 bound = vec4(65.24686921730095, 11.90274236858339, + 138.85323419021077, 55.34323805611308); + + // mercator -> (lon, lat) in radians + vec2 mercatorToLonLat(vec2 merc) { + float lon = merc.x * 2.0 * PI - PI; // 修正这里:-PI + float lat = 2.0 * atan(exp(PI - merc.y * 2.0 * PI)) - 0.5 * PI; + return vec2(lon, lat); + } + + vec2 rad2deg(vec2 r) { return r * (180.0 / PI); } + + void main() { + // 归一化墨卡托 -> 经纬度(度) + vec2 lonlat = rad2deg(mercatorToLonLat(v_merc)); + + // 经纬度 -> 纹理UV(按 bound 线性映射) + vec2 uv = vec2( + (lonlat.x - bound.x) / (bound.z - bound.x), + 1.0-(lonlat.y - bound.y) / (bound.w - bound.y) + ); + // 如果超出范围直接丢弃,避免采到边界垃圾 + if(any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) { + discard; } - // normalizedValue = clamp(normalizedValue, 0.0, 1.0); - - // 使用 LUT 进行颜色映射 + vec4 texColor = texture(u_tex, uv); + float value = texColor.r * 3.4; + value = clamp(value, 0.0, 1.0); + + // 软阈值,避免全场被 return + float alpha = smoothstep(0.07, 0.12, value) * 0.9; + if (alpha <= 0.001) discard; + vec4 lutColor = texture(u_lut, vec2(value, 0.5)); - // 添加一些透明度,使低值区域更透明 - // float alpha = smoothstep(0.0, 0.1, value); - float alpha = 0.7; fragColor = vec4(lutColor.rgb, alpha); - // fragColor = vec4(1.0,1.0,1.0,0.2); - }` + } + `; console.log(vertexSource, fragmentSource) diff --git a/hooks/use-timeline.ts b/hooks/use-timeline.ts index 1b5a1b8..1d2ec9a 100644 --- a/hooks/use-timeline.ts +++ b/hooks/use-timeline.ts @@ -34,7 +34,7 @@ export function useTimeline({ const [isPlaying, setIsPlaying] = useState(autoPlay) const [speed, setSpeed] = useState<'slow' | 'normal' | 'fast'>('normal') const intervalRef = useRef(null) - const { setTime, currentDatetime } = useMap() + const { setTime, currentDatetime, setTimelineTime, timelineDatetime } = useMap() const updateDate = useCallback((newDate: Date) => { setTime(newDate) @@ -117,6 +117,8 @@ export function useTimeline({ }, []) return { + timelineDatetime, + setTimelineTime, currentDatetime, isPlaying, speed,