Real-time global data visualization on a 3D interactive globe with Cesium.js - earthquakes, volcanoes, hurricanes, ISS tracking, wind patterns, and more
npm install global-data-screensaverA real-time global data visualization application built with React + TypeScript that displays earthquakes, volcanic activity, hurricanes, and atmospheric wind patterns on an interactive 3D globe with flowing streamlines.
Option 1: Install globally and run
``bash`
npm install -g global-data-screensaver
global-data-screensaver
Option 2: Run directly with npx (no installation)
`bash`
npx global-data-screensaver
The application will automatically:
- Start a local server on http://localhost:5173
- Open in your default browser
- Load real-time global data
Press Ctrl+C to stop the server.
`bashClone the repository
git clone https://github.com/teamaffixmb-jake/global-info-map.git
cd global-info-map
The application will open at
http://localhost:5173$3
`bash
Build the project
npm run buildPreview the production build
npm run preview
`The built files will be in the
dist/ directory, ready to deploy to any static hosting service.$3
- Node.js: v18.0.0 or higher
- npm: v9.0.0 or higher
- Modern web browser with WebGL 2.0 support
β¨ Key Features
- π Interactive 3D Globe - Powered by Cesium.js with smooth rotation, zoom, and tilt
- π Wind Flow Streamlines - Flowing curves with directional arrows showing global atmospheric circulation
- β‘ Real-Time Data - Live earthquakes (USGS), volcanoes (USGS), hurricanes (NOAA), ISS position
- π°οΈ ISS Tracking - Smooth 100Hz interpolation with dynamic orbit computation and double-buffered visualization
- π― Smart Filtering - Severity-based event filtering with minimizable UI
- β±οΈ Time Indicators - At-a-glance earthquake recency labels
- π Event Log - Sortable event history with click-to-zoom
- π Auto-Refresh - Intelligent update intervals (ISS: 1s, Hurricanes: 30s, Earthquakes: 60s)
- π¦ Rate Limit Handling - Graceful API rate limit detection with retry logic
- π Dark Mode - CartoDB Dark Matter tiles with starry night sky background
- βοΈ Autopilot Mode - Screensaver with rotate, wander, and ISS tracking modes
- π Auto-Switch - Random mode cycling every 30s-3min for hands-free screensaver
- π Camera Height Display - Real-time altitude indicator for spatial orientation
- π¨ Dynamic Marker Scaling - Markers scale with zoom level to prevent overlap
π¬οΈ Wind Streamline Visualization
$3
Streamlines are flowing curves that visualize atmospheric wind patterns by showing where air actually travels. Unlike static arrows, streamlines:
- Follow the flow field - Tangent to wind vectors at every point
- Show circulation patterns - Trade winds, westerlies, jet streams
- Are smooth and curved - RK2 integration for natural flow
- Have directional arrows - Clear indication of wind direction
- Are color-coded - Green (slow) to purple (fast) gradient
$3
- Sparse Grid Sampling: ~162 global data points (20Β° spacing)
- Bilinear Interpolation: Smooth wind estimation at any lat/lon
- RK2 Integration: 2nd-order Runge-Kutta for curved streamline tracing
- Quality Filtering: Removes artifacts and degenerate lines
- Dateline Handling: Proper splitting at 180Β°/-180Β° boundary
- Progressive Fetching: Shows real-time progress (0-100%)
$3
Wind data from Open-Meteo API with:
- 10m wind speed (mph)
- Wind direction (meteorological)
- Wind gusts
- Rate limit handling with 60s retry
π°οΈ ISS Tracking & Interpolation System
The International Space Station is tracked in real-time with smooth interpolation and a dynamic orbital path visualization.
$3
The system uses separated concerns with two independent loops:
#### 1. Data Update Loop (1 Hz - every 1 second)
Purpose: Fetch ISS data and compute orbital parameters
Operations:
- Fetches ISS position from API (lat, lon, altitude)
- Computes velocity vector from position change
- Calculates angular velocity:
Ο = v / r
- Determines orbital plane from position Γ velocity
- Updates orbit visualization with new trajectory
- Stores interpolation parametersLocation:
CesiumMarkerManager.updateISSData()#### 2. Rendering Loop (100 Hz - every 10ms)
Purpose: Smooth visual interpolation between API updates
Operations:
- Calculates elapsed time since last data update
- Computes angular displacement:
ΞΈ = Ο Γ Ξt
- Rotates ISS position along orbital plane
- Updates entity position for smooth motion
- Does NOT fetch data or compute orbitLocation:
CesiumMarkerManager.interpolateISSPosition()$3
The orbit is computed dynamically from real-time velocity data:
1. Velocity Vector:
v = current_position - previous_position
2. Radial Vector: Direction from Earth center to ISS
3. Orbit Normal: n = radial Γ velocity (perpendicular to orbital plane)
4. Circular Path: Great circle at ISS altitude in the orbital plane
5. No Hard-Coding: Inclination, orientation computed from actual motion$3
To prevent visual flicker during orbit updates, the system uses double-buffering:
How It Works:
- Two orbit paths exist:
orbitPathA and orbitPathB
- Both are created on initialization
- On each update, the oldest orbit is modified in place
- Tracks which orbit was last updated with currentOrbit: 'A' | 'B'
- While one orbit updates, the other remains visibleBenefits:
- β
No flicker - Always at least one orbit visible
- β
No entity deletion/creation - Just property updates
- β
Smooth transitions - Gradual orbit adjustments
- β
Performance - Reuses existing entities
Implementation: Alternates between updating orbitA and orbitB every second
$3
Velocity Calculation:
`typescript
velocityMagnitude = |positionβ - positionβ| // meters per second
angularVelocity = velocityMagnitude / orbitalRadius // radians per second
`Interpolation:
`typescript
angularDisplacement = angularVelocity Γ deltaTime
rotatedPosition = rotate(basePosition, orbitNormal, angularDisplacement)
`Result:
- ISS updates from API: 1 Hz (every second)
- ISS visual updates: 100 Hz (every 10ms)
- Smooth motion along computed orbital trajectory
- Realistic ~90 minute orbital period
- ISS velocity: ~7,660 m/s (27,576 km/h)
$3
The interpolation state is stored between updates:
`typescript
{
basePosition: Cartesian3, // Last known position
angularVelocity: number, // radians/ms
orbitalPlaneNormal: Cartesian3, // Perpendicular to orbit
orbitalRadius: number, // Earth radius + altitude
lastUpdateTime: number // Timestamp for Ξt
}
`π 3D Globe Features (v5.0.0)
This project now uses Cesium.js for photorealistic 3D globe visualization!
$3
- Natural perspective - See the Earth as it actually looks from space
- Better spatial understanding - True distances and relative positions
- WebGL performance - Hardware-accelerated rendering
- Camera freedom - Rotate, zoom, tilt to any angle
- True globe projection - No distortion at poles like Mercator
$3
- Cesium.js 1.137 - Industry-standard 3D geospatial platform
- Vanilla Cesium API - Direct integration for maximum control and stability
- Entity API - Type-safe entity management with automatic cleanup
- PolylineArrowMaterialProperty - Built-in directional arrows for streamlines
$3
Cesium uses longitude-first ordering (opposite of Leaflet):
`typescript
// Cesium (lon, lat, altitude)
Cartesian3.fromDegrees(lon, lat, heightInMeters)// vs. Leaflet (lat, lon)
L.marker([lat, lon])
`$3
Each data type is rendered as a Cesium entity:
- Earthquakes -
EllipseGraphics with magnitude-based sizing, color, and time labels
- Volcanoes - PolygonGraphics with triangular shapes
- Hurricanes - PointGraphics + LabelGraphics with category coloring
- ISS - PointGraphics + LabelGraphics at ~400km altitude with:
- Smooth 100Hz interpolation between 1Hz API updates
- Dynamic orbital path computed from real-time velocity
- Double-buffered cyan orbit visualization (no flicker)
- Realistic motion along computed trajectory
- Wind Streamlines - PolylineGraphics with arrow materials and speed-based coloring$3
- Left Click + Drag - Rotate globe
- Right Click + Drag - Pan camera
- Scroll Wheel - Zoom in/out
- Middle Click + Drag - Tilt camera angle
$3
The globe now features a dark-themed base layer using CartoDB Dark Matter tiles for:
- Reduced eye strain during extended viewing
- Better contrast for event markers
- Professional aesthetic
- Starry night sky background
The dark theme is applied by default through Cesium's imagery provider configuration:
`typescript
viewer.imageryLayers.addImageryProvider(
new UrlTemplateImageryProvider({
url: 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png',
// ...
})
);
`$3
A real-time camera height display is shown in the top-left corner, indicating the distance from the Earth's surface in kilometers or meters:
- Above 1000m: Shows in kilometers (e.g., "Camera: 7,234 km")
- Below 1000m: Shows in meters (e.g., "Camera: 847 m")
- Updates dynamically as you zoom in/out
- Helps with spatial orientation on the 3D globe
$3
Markers automatically scale with camera height to maintain consistent visual size and prevent overlap:
- Zoomed Out: Markers appear appropriately sized for global view
- Zooming In: Markers progressively shrink to resolve overlapping events
- Linear Scaling: Below 7M meters, size scales linearly with camera height
- Capped Maximum: Above 7M meters, size is capped to prevent excessive growth
This allows you to:
- View global patterns when zoomed out
- Resolve individual events that occurred at nearly identical locations
- Always keep markers within screen bounds
Technical implementation:
`typescript
const baseScale = cameraHeight / 7_000_000; // Reference height: 7M meters
const scale = Math.max(0.3, Math.min(baseScale, 1.0));
marker.setRadius(baseSize * scale);
`$3
Autopilot is a screensaver-like feature that automates camera movement for hands-free viewing. Enable it with the checkbox in the top-left corner.
#### Available Modes
Manual Mode Selection
When Autopilot is enabled, choose from three modes:
1. π Rotate Mode
- Slow, continuous globe rotation
- Smooth right-to-left motion at current altitude
- Camera adjusts to 8Mm altitude for optimal viewing
- Rotates around current location after coming from other modes
2. π² Wander Mode
- Intelligently explores events based on severity
- Probabilistic selection: Weights events by severity (exponential: 2^severity)
- Recently visited tracking: Avoids revisiting same events (keeps last 20)
- Automatic rotation: Camera orbits around each selected event
- 10-second intervals: Switches to new event every 10 seconds
- Smart filtering: Excludes ISS and recently visited events
- Clears history: Resets when all events have been visited
3. π°οΈ ISS Tracking Mode
- Tracks the International Space Station in real-time
- Smooth interpolation: 100Hz rendering with velocity-based positioning
- Dynamic orbit: Computes orbital path from instantaneous velocity
- Camera tracking: Follows ISS with auto-rotation around it
- Optimal distance: 3Mm back, 2Mm up from ISS
- 1-second updates: Fetches new position data every second
#### Auto-Switch Mode π
Enable Auto-Switch for a hands-free screensaver that cycles through all modes automatically.
How it works:
- Toggle "Auto-Switch" checkbox (appears when Autopilot is enabled)
- Random intervals: Switches modes every 30 seconds to 3 minutes
- Random mode selection: Picks between Rotate, Wander, and ISS modes
- Mode radio buttons hidden: When Auto-Switch is on, manual mode selection is disabled
- Automatic restart: Each mode switch generates a new random interval
- Console logging: Shows which mode is active and time until next switch
Example flow:
`
Auto-Switch enabled
β Starts in Rotate mode (random: 45 seconds)
β Switches to Wander mode (random: 2 minutes 15 seconds)
β Switches to ISS mode (random: 1 minute 30 seconds)
β Switches to Rotate mode (random: 50 seconds)
β Continues indefinitely...
`#### Technical Implementation
`typescript
// State management
const [autopilotEnabled, setAutopilotEnabled] = useState(false);
const [autopilotMode, setAutopilotMode] = useState<'rotate' | 'wander' | 'iss'>('rotate');
const [autoSwitchEnabled, setAutoSwitchEnabled] = useState(false);// Auto-switch logic with random intervals
useEffect(() => {
if (!autopilotEnabled || !autoSwitchEnabled) return;
const switchMode = () => {
const modes = ['rotate', 'wander', 'iss'];
const randomMode = modes[Math.floor(Math.random() * modes.length)];
setAutopilotMode(randomMode);
// Random delay: 30s to 3min
const delay = Math.floor(Math.random() * 150000) + 30000;
setTimeout(switchMode, delay);
};
switchMode(); // Start immediately
}, [autopilotEnabled, autoSwitchEnabled]);
// Mode execution
useEffect(() => {
if (!autopilotEnabled || !mapController) return;
switch (autopilotMode) {
case 'rotate':
mapController.adjustAltitudeForRotation();
setTimeout(() => mapController.startRotation(), 2000);
break;
case 'wander':
// Probabilistic event selection and navigation
break;
case 'iss':
mapController.startISSTracking();
break;
}
return () => {
mapController.stopRotation();
mapController.stopISSTracking();
if (autopilotMode === 'wander') {
mapController.clearSelection();
}
};
}, [autopilotEnabled, autopilotMode, mapController]);
`Key Features:
- β
Mutually exclusive modes (only one active at a time)
- β
Clean transitions with proper cleanup
- β
Rotation timeout tracking for mode switching
- β
Deselects entities when leaving wander mode
- β
Auto-Switch provides fully automated screensaver experience
ποΈ Architecture Overview
This project uses a unified DataPoint architecture where all data types flow through a single processing pipeline. This design provides:
- Type-safe data processing with TypeScript
- Persistent tracking of events by unique IDs
- Efficient updates - markers update in place rather than being recreated
- Unified processing - all data types handled consistently
- Easy extensibility - adding new data types is straightforward
$3
1. Single Source of Truth: All data is converted to
DataPoint objects
2. Type Safety: Full TypeScript coverage with strict type checking
3. Immutable Updates: Markers are compared by ID and only updated when changed
4. Separation of Concerns: Data fetching, conversion, rendering, and event logging are separate
5. Fallback Strategy: Sample data generators provide resilience when APIs fail
6. Performance First: Concurrent fetch prevention, efficient rendering, quality filtering---
π Project Structure
`
src/
βββ App.tsx # Main application component with data orchestration
βββ App.css # Global styles and animations
βββ main.tsx # React entry point
βββ components/
β βββ CesiumMap.tsx # 3D globe initialization and CesiumMarkerManager integration
β βββ Legend.tsx # Data legend with counts, minimizable UI, simulated data toggle
β βββ EventLog.tsx # Event logging with severity filtering and clear button
βββ models/
β βββ DataPoint.ts # Unified data model with TypeScript types
βββ types/
β βββ raw.ts # Raw API response type definitions
βββ utils/
β βββ api.ts # Data fetching with progress tracking and rate limiting
β βββ converters.ts # Raw data β DataPoint conversion with types
β βββ CesiumMarkerManager.ts # Unified entity rendering pipeline + streamline renderer
β βββ streamlines.ts # Wind flow visualization algorithms
β βββ severity.ts # Severity calculation with enums
β βββ helpers.ts # Color/size/formatting utilities
β βββ animations.ts # Marker animation functions
βββ vite-env.d.ts # Vite type declarations
`---
π Data Flow
`
1. App.tsx calls API fetch functions (utils/api.ts)
β
2. Typed API responses returned (APIResponse)
- Progress callbacks for wind data (0-100%)
- Rate limit detection and handling
β
3. Converters (utils/converters.ts) transform raw data β DataPoints
β
4. DataPoints passed to CesiumMarkerManager (utils/CesiumMarkerManager.ts)
β
5. CesiumMarkerManager compares with existing entities by ID
β
6. Updates existing entities OR adds new entities OR removes old entities
β
7. Wind data β Streamline generation β Polyline rendering
β
8. New events logged to EventLog if severity threshold met
`---
π§© Core Components
$3
Purpose: Unified, type-safe data structure for all event types.
Key Features:
- TypeScript Enums:
DataSourceType enum for all event types
- Type Discrimination: Separate metadata interfaces for each data type
- Generic Types: DataPointKey Fields:
`typescript
class DataPoint {
id: string; // Unique identifier with type prefix
type: DataSourceType; // Event type from enum
lat: number; // Latitude
lon: number; // Longitude
title: string; // Display title
description: string; // Detailed description
severity: number; // 1-4: LOW, MEDIUM, HIGH, CRITICAL
timestamp: number; // Event timestamp
emoji: string; // Display emoji
metadata: T; // Type-specific additional data
lastUpdated: number; // Last update timestamp
}
`Metadata Types:
-
EarthquakeMetadata - magnitude, depth, place, url
- ISSMetadata - altitude, velocity
- VolcanoMetadata - elevation, country, alertLevel, lastEruption
- HurricaneMetadata - category, windSpeed, pressure, direction
- And more...ID Prefixes:
-
eqk - Earthquakes
- iss - International Space Station (single ID)
- vol - Volcanoes
- hur - Hurricanes
- tor - Tornadoes
- aur - Aurora activity
- wnd - Wind patterns (not used for streamlines)
- prc - Precipitation
- rkt - Rocket launches
- cfl - Conflicts
- prt - Protests
- unr - Social unrest
- dis - Disease outbreaksKey Methods:
-
hasChanged(other: DataPoint | null): boolean - Compare with another DataPoint
- isNew(): boolean - Check if event occurred within last 10 minutes
- isRecent(): boolean - Check if event occurred within last hour
- getAge(): number - Get age in milliseconds---
$3
Purpose: Generate flowing wind visualization using computational fluid dynamics techniques.
Core Functions:
`typescript
// Interpolate wind at any lat/lon from sparse grid
export function interpolateWind(
lat: number,
lon: number,
windData: RawWind[]
): { direction: number; speed: number } | null// Trace a streamline from a seed point
export function traceStreamline(
seedLat: number,
seedLon: number,
windData: RawWind[],
maxSteps: number,
stepSize: number
): Streamline | null
// Generate seed points for streamline starts
export function generateSeedPoints(
spacing: number,
jitter: number
): Array<{ lat: number; lon: number }>
// Color gradient for wind speed
export function getWindColor(speed: number): string // Green β Purple
// Opacity based on wind strength
export function getWindOpacity(speed: number): number // 0.4 β 0.8
`Algorithm Details:
- Inverse Distance Weighting: Interpolates wind from 4 nearest neighbors
- RK2 Integration: Runge-Kutta 2nd order for smooth curve tracing
- Adaptive Step Size: 0.6Β° steps for fine-grained sampling
- Quality Filtering: Rejects artifacts based on curvature, distance, speed
- Dateline Handling: Splits streamlines at longitude Β±180Β° boundary
Performance Optimizations:
- Sparse grid to minimize API calls (~162 points globally)
- Progressive fetching with 200ms delays between requests
- Concurrent fetch prevention to avoid rate limits
- Efficient distance calculations with early termination
---
$3
Purpose: Transform raw API data into standardized, typed DataPoint objects.
Type Definitions:
Each data source has defined raw types in
src/types/raw.ts:
`typescript
export interface RawEarthquake {
id?: string;
geometry: { coordinates: [number, number, number?] };
properties: {
mag: number;
place: string;
time: number;
url?: string;
};
}export interface RawWind {
lat: number;
lon: number;
speed: number;
direction: number;
gusts: number;
time: number;
}
// ... similar interfaces for all data types
`Converter Functions:
-
earthquakeToDataPoint(eq: RawEarthquake): DataPoint
- issToDataPoint(iss: RawISS): DataPoint
- volcanoToDataPoint(volcano: RawVolcano): DataPoint
- hurricaneToDataPoint(hurricane: RawHurricane): DataPoint
- etc.Responsibilities:
1. Extract or generate unique IDs
2. Calculate severity using severity.ts functions
3. Map raw fields to DataPoint structure
4. Preserve metadata for type-specific rendering
Helper Function:
`typescript
export function convertBatch(
rawData: T[] | null | undefined,
converter: (item: T) => DataPoint
): DataPoint[]
`---
$3
Purpose: Unified, type-safe pipeline for processing and rendering all entity types + wind streamlines on the 3D globe.
Core Functionality:
`typescript
export class CesiumMarkerManager {
private viewer: Cesium.Viewer;
private addEventCallback: AddEventCallback | null;
private severityThreshold: number;
private markers: Map;
private loggedEventIds: Set;
private streamlines: L.Polyline[];
private windGridData: RawWind[];
// Process array of DataPoints
processDataPoints(dataPoints: DataPoint[]): void
// Render wind as streamlines (not markers!)
renderWindStreamlines(
windData: RawWind[],
setLoadingMessage?: (msg: string) => void
): void
// Compare by ID and update/add/remove as needed
private processDataPoint(dataPoint: DataPoint): void
// Create type-specific Cesium entities
private createMarker(dataPoint: DataPoint): L.Marker | L.CircleMarker | null
}
`Type Definitions:
`typescript
export type AddEventCallback = (
type: string,
emoji: string,
title: string,
message: string,
lat: number,
lon: number,
data: { markerId?: string; [key: string]: any },
severity: number
) => void;interface MarkerEntry {
marker: L.Marker | L.CircleMarker;
dataPoint: DataPoint;
timeLabel?: L.Marker; // For earthquake time indicators
}
`Key Features:
- Efficient Updates: Only modifies changed markers
- ID Tracking: Maintains map of ID β marker for quick lookups
- Event Logging: Automatically logs new/updated events based on severity
- Type-Specific Rendering: Each data type has custom marker styling
- Animation Support: Integrates with animations.ts for new events
- Streamline Rendering: Generates and renders flowing wind patterns
- Quality Filtering:
isValidStreamline() removes artifacts
- Dateline Splitting: splitStreamlineAtDateline() handles Β±180Β° wrappingEarthquake Time Labels π:
- Automatically added to each earthquake marker
- Shows concise time format: "5m ago", "2h ago", "3d ago"
- Color-matched to earthquake severity
- Positioned to the right of marker
- Non-interactive, semi-transparent
---
$3
Purpose: Fetch data from external APIs with typed responses, progress tracking, and rate limit handling.
Type Definition:
`typescript
export interface APIResponse {
success: boolean;
data: T;
}
`Real Data Sources:
- β
Earthquakes: USGS - Magnitude 2.5+ from last 24h
- β
ISS Position: wheretheiss.at - Real-time orbital position
- β
Volcanoes: USGS Volcano Hazards Program - US volcano alerts
- β
Hurricanes: NOAA NHC (via CORS proxy) - Active tropical cyclones
- β
Wind Patterns: Open-Meteo - Global wind data with sparse grid sampling
Simulated Data (Toggle in Legend):
- Tornadoes, Aurora, Precipitation, Rocket Launches, Conflicts, Protests, Social Unrest, Disease Outbreaks
Advanced Features:
Progressive Wind Fetching:
`typescript
export async function fetchWindPatterns(
onRateLimitCallback?: () => void,
onProgressCallback?: (percentage: number) => void
): Promise>
`
- Reports progress: 0% β 100%
- Sequential fetching with 200ms delays
- Rate limit detection (429 status)
- Automatic 60s retry on rate limit
- Concurrent fetch preventionCORS Proxy for NOAA:
`typescript
const response = await fetch(
'https://corsproxy.io/?' + encodeURIComponent('https://www.nhc.noaa.gov/CurrentStorms.json')
);
`Fallback Strategy:
`typescript
export async function fetchEarthquakes(): Promise> {
try {
const response = await fetch(USGS_API);
return { success: true, data: response };
} catch (error) {
return { success: false, data: generateSampleEarthquakes() };
}
}
`---
$3
Purpose: Calculate severity levels for event filtering using TypeScript enums.
Severity Enum:
`typescript
export enum SEVERITY {
LOW = 1,
MEDIUM = 2,
HIGH = 3,
CRITICAL = 4
}export const SEVERITY_LABELS: Record = {
1: 'Low',
2: 'Medium',
3: 'High',
4: 'Critical'
};
`Calculation Functions:
Each data type has a dedicated severity calculator:
`typescript
export function getEarthquakeSeverity(magnitude: number): SEVERITY
export function getHurricaneSeverity(category: number): SEVERITY
export function getTornadoSeverity(intensity: number): SEVERITY
export function getVolcanicSeverity(alertLevel: string): SEVERITY
// etc.
`Usage in Event Log:
The EventLog component filters events based on a user-selected severity threshold, showing only events meeting or exceeding that level.
---
$3
#### App.tsx
Role: Main application orchestrator with full type safety
Key State:
`typescript
const [loading, setLoading] = useState(true);
const [loadingStatus, setLoadingStatus] = useState('');
const [windRateLimited, setWindRateLimited] = useState(false);
const [showSimulatedData, setShowSimulatedData] = useState(false);
const windFetchInProgressRef = useRef(false);
`Key Features:
- Parallel data fetching with
Promise.all()
- Asynchronous wind streamline rendering
- Rate limit detection with retry logic
- Concurrent fetch prevention
- 60-second auto-refresh
- Loading progress indicatorsResponsibilities:
- Fetch data from all sources
- Convert raw data to DataPoints
- Manage global state
- Pass typed props to child components
#### Map.tsx
Role: Map initialization and marker management
Props Interface:
`typescript
interface MapProps {
dataPoints: DataPoint[];
addEvent: AddEventCallback;
severityThreshold: number;
setMapController: (controller: MapController) => void;
markerManagerRef: MutableRefObject;
}
`Important: Uses multiple
useEffect hooks with specific dependencies to prevent map flickering.#### Legend.tsx
Role: Display legend, data counts, and controls
Props Interface:
`typescript
interface LegendProps {
counts?: Record;
lastUpdate?: string;
showSimulatedData?: boolean;
onSimulatedDataToggle?: (show: boolean) => void;
}
`Features:
- Shows real-time counts for each data type
- Minimizable UI
- Last update timestamp
- Simulated Data Toggle - Enable/disable sample data
- Color/symbol key for all data types
#### EventLog.tsx
Role: Display event log with filtering
Props Interface:
`typescript
interface EventLogProps {
events: EventData[];
onEventClick?: (event: EventData) => void;
severityThreshold: number;
onSeverityChange: (threshold: number) => void;
onClearEvents?: () => void;
}
`Features:
- Shows last 100 events
- Auto-scrolls only if already at bottom
- Severity filtering dropdown
- Click to zoom to event location
- Clear button - Remove all events
- Minimizable transparent panel
---
π¨ Styling & UI
$3
- Background: Dark theme (#111827)
- Panels: Semi-transparent with backdrop blur
- Markers: Color-coded by severity/intensity
- Wind Streamlines: Green (5 mph) β Purple (60+ mph) gradient$3
- Earthquakes: Circle markers with time labels, size by magnitude, color by magnitude
- Volcanoes: Triangle markers, color by alert level (red/orange/yellow/gray)
- Hurricanes: Spinning cyclone emoji, size by category, color-coded
- ISS: Animated rotating satellite icon with yellow pulsing beacon circles
- Wind Streamlines: Flowing polylines with directional arrows, green-to-purple gradient$3
- Bounce: New events bounce to draw attention
- Pulse: Very recent events pulse continuously
- Rotate: ISS icon rotates continuously
- Spin: Hurricane icons spin continuously$3
- Main loading screen with spinning animation
- Progressive wind fetch: "Fetching wind data: 45%"
- Streamline generation: "Generating wind streamlines..."
- Rate limit warning: Red popup with 60s countdown---
π Development Setup
$3
- Node.js 18+
- npm or yarn$3
`bash
Install dependencies
npm installStart development server
npm run devBuild for production
npm run buildPreview production build
npm run previewType check
npx tsc --noEmit
`$3
The app runs at http://localhost:5173/ (or 5174 if port is busy) with hot module reload.$3
- React 18 - UI framework
- TypeScript 5 - Type safety and tooling
- Vite - Build tool and dev server
- Cesium.js 1.137 - Interactive 3D globe with WebGL rendering
- @types/react, @types/leaflet, @types/node - Type definitions---
π Common Issues & Solutions
$3
Cause: Dependencies in Map.tsx useEffect causing constant reinitialization
Solution: Use useCallback for functions passed as props, split effects by concern$3
Cause: Open-Meteo API has request limits
Solution:
- App detects 429 errors automatically
- Shows red warning popup with countdown
- Returns partial data collected before rate limit
- Retries on next 60s refresh
- Concurrent fetch prevention$3
Cause: NOAA API blocks direct browser requests
Solution: Uses corsproxy.io to proxy requests$3
Cause: Streamlines crossing dateline (180Β°/-180Β°)
Solution:
- splitStreamlineAtDateline() detects large longitude jumps
- Renders each segment separately
- Skips arrows across dateline$3
Cause: CesiumMarkerManager not receiving updated DataPoints
Solution: Check that converters are creating stable IDs$3
Cause: Severity threshold too high
Solution: Lower severity threshold in EventLog dropdown$3
Cause: Type mismatches or missing type definitions
Solution: Run npx tsc --noEmit to see all errors, ensure proper types are defined---
π Performance Considerations
$3
- Markers are tracked by ID in a Map
- Only changed markers are updated (compare by DataPoint.hasChanged())
- Old markers are removed when IDs disappear from data
- Time labels managed alongside markers$3
- Quality filtering removes ~30-40% of generated streamlines
- Dateline splitting creates multiple smaller polylines
- Arrows placed every 10 points (not every point)
- Cesium's PolylineArrowMaterialProperty provides efficient directional arrows$3
- Event log keeps only last 100 events
- Marker cleanup in useEffect return functions
- Clear intervals for animations on marker removal
- Streamlines and arrows cleaned up on data refresh$3
- Auto-refresh every 60 seconds
- All API calls made in parallel with Promise.all()
- Wind data fetched asynchronously after main data
- Non-blocking updates with React state
- Concurrent fetch prevention with ref-based locks$3
- Sequential wind fetching with 200ms delays
- Sparse grid sampling (162 points vs thousands)
- 429 detection stops fetching immediately
- 60-second cooldown before retry
- Graceful degradation with partial data$3
- Catch errors at compile time instead of runtime
- Better IDE autocomplete and IntelliSense
- Refactoring is safer with type checking
- Self-documenting code with interfaces---
π― Future Enhancements
$3
1. TypeScript Migration - Full type safety (v3.0.0)
2. Real API Integration - Earthquakes, ISS, Volcanoes, Hurricanes, Wind
3. Wind Streamlines - Advanced atmospheric visualization
4. Rate Limit Handling - Graceful 429 error recovery
5. Time Indicators - Earthquake recency labels
6. Simulated Data Toggle - User control over sample data
7. Event Log Clearing - Clear button for event history
8. 3D Globe Conversion - Complete! Migrated to Cesium.js (v5.0.0)
9. Dark Mode - CartoDB Dark Matter tiles with starry sky background
10. Camera Height Display - Real-time altitude indicator
11. Dynamic Marker Scaling - Zoom-aware marker sizing to prevent overlap
12. Autopilot Rotate Mode - Automated globe rotation for screensaver mode
13. Autopilot Wander Mode - Probabilistic event exploration based on severity
14. Autopilot ISS Mode - Real-time ISS tracking with camera following
15. ISS Smooth Interpolation - 100Hz rendering with velocity-based orbit computation
16. Double-Buffered Orbit Visualization - Flicker-free dynamic orbital path updates
17. Staggered Data Update Intervals - Optimized refresh rates per data source$3
3. WebSocket Updates - Real-time data streaming instead of polling
4. Historical Data - Track and visualize event history over time
5. Storm Tracking - Show hurricane/tornado paths with historical positions
6. User Preferences - Save settings (severity, visible layers, theme)
7. Mobile Optimization - Improve touch interactions and performance
8. Offline Mode - Cache data for offline viewing with service workers
9. Export Functionality - Export event data (CSV/JSON) or screenshots
10. Backend Proxy - Dedicated proxy server to eliminate CORS issues
11. Unit Tests - Add comprehensive test coverage with Vitest
12. Storybook - Component documentation and playground
13. Time Slider - Scrub through historical earthquake/weather data
14. Multiple Globe Themes - Toggle between different tile styles and skyboxes
15. Altitude Visualization - 3D height based on earthquake depth
16. Performance Profiling - Optimize for lower-end devices
17. Entity Clustering - Group nearby events when zoomed out
18. Improved Selection UX - Better click handling and camera movement---
π Code Style Guide
$3
- Components: PascalCase (e.g., EventLog.tsx)
- Utilities: camelCase (e.g., converters.ts, streamlines.ts)
- Types/Interfaces: PascalCase (e.g., DataPoint, MapProps)
- Enums: PascalCase (e.g., DataSourceType, SEVERITY)
- Constants: UPPER_SNAKE_CASE (e.g., SEVERITY_LABELS)
- CSS Classes: kebab-case (e.g., event-log, streamline-arrow)$3
- One component per file
- Group related utilities in same file
- Keep files under 1000 lines when possible
- Extract reusable logic to utility functions
- Co-locate types with their usage in types/ folder$3
- Use interfaces for object shapes
- Use enums for fixed sets of values
- Use type unions for discriminated unions
- Prefer explicit return types for public APIs
- Use generics for reusable components
- Avoid any - use unknown or proper types$3
- Use functional components with hooks
- Use useCallback for functions passed as props
- Use useRef for values that don't trigger re-renders
- Split complex effects into multiple focused effects
- Define prop interfaces for all components
- Wrap async operations in try/catch$3
- Memoize expensive calculations with useMemo
- Debounce rapid user interactions
- Use useCallback to prevent re-renders
- Lazy load components when appropriate
- Batch state updates---
π API Keys & Secrets
This project uses public, free APIs that don't require authentication:
- USGS Earthquake API - No key needed
- wheretheiss.at - No key needed
- USGS Volcanoes - No key needed
- NOAA Hurricane Center - No key needed (using CORS proxy)
- Open-Meteo - No key needed (rate limited to ~600 requests/day)
Note: If you exceed Open-Meteo's rate limit, the app will:
1. Detect the 429 error
2. Show a warning popup
3. Return partial wind data
4. Automatically retry after 60 seconds
---
π€ Contributing
When contributing to this project:
1. Understand the architecture - Read this README fully
2. Follow the data flow - Raw data β DataPoint β CesiumMarkerManager β Render
3. Maintain type safety - All new code should be properly typed
4. Test with sample data - Ensure fallbacks work
5. Add severity calculations - New data types need severity functions
6. Update documentation - Keep this README current
7. Consider performance - Avoid unnecessary re-renders
8. Run type checks -
npx tsc --noEmit before committing
9. Test rate limits - Ensure graceful degradation
10. Check dateline crossings - Test with global data---
π License
This project is open source and available for educational and demonstration purposes.
---
π Credits & Data Sources
$3
- Earthquake Data: USGS Earthquake Hazards Program
- ISS Position: wheretheiss.at API
- Volcanic Activity: USGS Volcano Hazards Program
- Hurricane Data: NOAA National Hurricane Center
- Wind Data: Open-Meteo
- CORS Proxy: corsproxy.io$3
- Map Tiles: CartoDB Dark Matter
- Cesium.js: Open-source 3D globe and mapping platform
- React: UI framework by Meta
- TypeScript: Static typing by Microsoft
- Vite: Build tool by Evan You$3
- Streamline Visualization: Based on computational fluid dynamics techniques
- RK2 Integration: Runge-Kutta 2nd order numerical integration
- Inverse Distance Weighting: Spatial interpolation technique---
Last Updated: January 2026
Version: 5.2.2 (Auto-Switch & NPM Package)
Status: Production Ready
Recent Changes: Auto-Switch mode with random intervals (30s-3min), npm package with CLI, static file server for global install, ISS smooth interpolation (100Hz), dynamic orbit computation, double-buffered visualization, autopilot wander & ISS modes, staggered data updates
Milestone: Fully automated screensaver with intelligent mode cycling! Available as npm package:
npm install -g global-data-screensaver`