-
Notifications
You must be signed in to change notification settings - Fork 41
feat: scaffold initial frontend project structure and marketing layou… #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import React, { useEffect, useRef } from "react"; | ||
| import Globe from "globe.gl"; | ||
| import * as satellite from "satellite.js"; | ||
|
|
||
| const EARTH_RADIUS_KM = 6371; // km | ||
| const TIME_STEP = 3 * 1000; // per frame | ||
| const radiansToDegrees = (rad: number) => (rad * 180) / Math.PI; | ||
|
|
||
| interface SatDataItem { | ||
| satrec: satellite.SatRec; | ||
| name: string; | ||
| lat: number; | ||
| lng: number; | ||
| alt: number; | ||
| } | ||
|
|
||
| export const GlobeBackground: React.FC = () => { | ||
| const containerRef = useRef<HTMLDivElement>(null); | ||
| const globeInstanceRef = useRef<any>(null); | ||
| const animationFrameRef = useRef<number | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| if (!containerRef.current) return; | ||
|
|
||
| // Initialize Globe | ||
| const world = new Globe(containerRef.current) | ||
| .globeImageUrl("//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg") | ||
| .backgroundColor("rgba(0,0,0,0)") | ||
| .particleLat("lat") | ||
| .particleLng("lng") | ||
| .particleAltitude("alt") | ||
| .particlesColor(() => "#00e5ff") | ||
| .showAtmosphere(true) | ||
| .atmosphereColor("#00e5ff") | ||
| .atmosphereAltitude(0.25); | ||
|
|
||
| // Disable zoom | ||
| world.controls().enableZoom = false; | ||
|
|
||
| globeInstanceRef.current = world; | ||
|
|
||
| // Set initial view | ||
| setTimeout(() => { | ||
| world.pointOfView({ altitude: 3.0 }, 1000); | ||
| }, 200); | ||
|
|
||
| // Resize handler | ||
| const handleResize = () => { | ||
| if (containerRef.current) { | ||
| world.width(containerRef.current.clientWidth); | ||
| world.height(containerRef.current.clientHeight); | ||
| } | ||
| }; | ||
| window.addEventListener("resize", handleResize); | ||
| handleResize(); | ||
|
|
||
| // Fetch and propagate satellites | ||
| let isMounted = true; | ||
| fetch("/datasets/space-track-leo.txt") | ||
| .then((r) => { | ||
| if (!r.ok) { | ||
| throw new Error(`Failed to fetch dataset: ${r.statusText}`); | ||
| } | ||
| return r.text(); | ||
| }) | ||
| .then((rawData) => { | ||
| if (!isMounted) return; | ||
|
|
||
| const tleData = rawData | ||
| .replace(/\r/g, "") | ||
| .split(/\n(?=[^12])/) | ||
| .filter((d) => d) | ||
| .map((tle) => tle.split("\n")); | ||
|
|
||
| const satData: SatDataItem[] = tleData | ||
| .map(([name, ...tle]) => { | ||
| if (!tle[0] || !tle[1]) return null; | ||
| try { | ||
| return { | ||
| satrec: satellite.twoline2satrec(tle[0], tle[1]), | ||
| name: name.trim().replace(/^0 /, ""), | ||
| lat: 0, | ||
| lng: 0, | ||
| alt: 0, | ||
| }; | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| }) | ||
| .filter((d): d is SatDataItem => { | ||
| if (!d) return false; | ||
| const pos = satellite.propagate(d.satrec, new Date()); | ||
| return !!(pos && typeof pos === "object" && pos.position); | ||
| }); | ||
|
|
||
| // Time ticker loop | ||
| let time = new Date(); | ||
|
|
||
| const frameTicker = () => { | ||
| if (!isMounted) return; | ||
|
|
||
| time = new Date(+time + TIME_STEP); | ||
|
|
||
| // Update satellite positions | ||
| const gmst = satellite.gstime(time); | ||
| satData.forEach((d) => { | ||
| const eci = satellite.propagate(d.satrec, time); | ||
| if (eci && typeof eci === "object" && eci.position) { | ||
| const position = eci.position as satellite.PositionAndVelocity["position"]; | ||
| if (typeof position === "object" && "x" in position) { | ||
| const gdPos = satellite.eciToGeodetic(position, gmst); | ||
| d.lat = radiansToDegrees(gdPos.latitude); | ||
| d.lng = radiansToDegrees(gdPos.longitude); | ||
| d.alt = gdPos.height / EARTH_RADIUS_KM; | ||
| } | ||
| } else { | ||
| d.lat = NaN; | ||
| d.lng = NaN; | ||
| d.alt = NaN; | ||
| } | ||
| }); | ||
|
|
||
| const validSats = satData.filter( | ||
| (d) => !isNaN(d.lat) && !isNaN(d.lng) && !isNaN(d.alt) | ||
| ); | ||
| world.particlesData([validSats]); | ||
|
|
||
| // Rotate globe slowly | ||
| const currentPov = world.pointOfView(); | ||
| world.pointOfView({ | ||
| lng: currentPov.lng + 0.05, | ||
| }); | ||
|
|
||
| animationFrameRef.current = requestAnimationFrame(frameTicker); | ||
| }; | ||
|
Comment on lines
+96
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
awk 'substr($0, 1, 2) == "1 " { count++ } END { print count }' \
frontend/public/datasets/space-track-leo.txtRepository: 7-Blocks/Kepler Length of output: 159 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- GlobeBackground.tsx (relevant range) ---'
nl -ba frontend/src/components/GlobeBackground.tsx | sed -n '1,220p'
echo
echo '--- satellite dataset line count / sample shape ---'
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/public/datasets/space-track-leo.txt')
count = 0
sample = []
with p.open() as f:
for i, line in enumerate(f, 1):
if i <= 6:
sample.append(line.rstrip('\n'))
if line.startswith('1 '):
count += 1
print('line_count_starting_with_1_space =', count)
print('first_lines:')
for s in sample:
print(repr(s))
PYRepository: 7-Blocks/Kepler Length of output: 238 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the satellite data loader and animation setup in the component.
ast-grep outline frontend/src/components/GlobeBackground.tsx --view expandedRepository: 7-Blocks/Kepler Length of output: 496 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the surrounding effect setup to understand how often this runs.
sed -n '1,260p' frontend/src/components/GlobeBackground.tsx | nl -ba | sed -n '1,260p'Repository: 7-Blocks/Kepler Length of output: 193 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the component around the animation loop only.
nl -ba frontend/src/components/GlobeBackground.tsx | sed -n '80,160p'Repository: 7-Blocks/Kepler Length of output: 193 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- GlobeBackground.tsx around the animation loop ---'
sed -n '1,220p' frontend/src/components/GlobeBackground.tsx | sed -n '1,220p' | awk '{printf "%4d %s\n", NR, $0}' | sed -n '70,170p'
echo
echo '--- Find the dataset loader / fetch path ---'
rg -n "space-track-leo|particlesData|requestAnimationFrame|TIME_STEP|pointOfView|SatDataItem" frontend/src/components/GlobeBackground.tsx frontend/src -SRepository: 7-Blocks/Kepler Length of output: 6131 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/src/components/GlobeBackground.tsx')
for i, line in enumerate(p.read_text().splitlines(), 1):
if 80 <= i <= 170:
print(f"{i:4d} {line}")
PYRepository: 7-Blocks/Kepler Length of output: 3227 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/src/components/GlobeBackground.tsx')
for i, line in enumerate(p.read_text().splitlines(), 1):
if 1 <= i <= 220:
if 'particlesData' in line or 'requestAnimationFrame' in line or 'TIME_STEP' in line or 'pointOfView' in line or 'space-track-leo' in line:
print(f"{i:4d} {line}")
PYRepository: 7-Blocks/Kepler Length of output: 579 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('frontend/public/datasets/space-track-leo.txt')
count = 0
valid = 0
with p.open() as f:
for line in f:
if line.startswith('1 '):
count += 1
if line.startswith('2 '):
valid += 1
print('line_count_starting_with_1_space =', count)
print('line_count_starting_with_2_space =', valid)
PYRepository: 7-Blocks/Kepler Length of output: 235 Throttle globe updates to elapsed time. This propagates the full 16,071-satellite catalog and calls 🤖 Prompt for AI Agents |
||
|
|
||
| frameTicker(); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("Error loading or running globe satellites:", err); | ||
| }); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| window.removeEventListener("resize", handleResize); | ||
| if (animationFrameRef.current) { | ||
| cancelAnimationFrame(animationFrameRef.current); | ||
| } | ||
| if (containerRef.current) { | ||
| containerRef.current.innerHTML = ""; | ||
| } | ||
| }; | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div className="absolute inset-0 w-full h-full overflow-hidden select-none"> | ||
| <div ref={containerRef} className="w-full h-full" /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default GlobeBackground; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
particlesDatacall wrapsvalidSatsin an extra array, so globe.gl receives a single-element array whose sole item is the wholeSatDataItem[]array. When globe.gl iterates the data and tries to readd["lat"]/d["lng"]/d["alt"]on that array, it getsundefinedfor all fields, so no satellite particles will ever be rendered.Prompt To Fix With AI