-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(frontend): ✨ responsive, camera follows node, no interaction enables on globe, select satellite on dropdown #120 * frontpage #120 * testing * show to pradipta * fix styling for satData home * feat: add borders and background color * feat(backend): Use zustand store to save selected satellite and satellites in combobox * globe * feat(frontend): satellite color and selection, finish globe * lint and prettier * small fix * move camera to selected satellite * feat(frontend): ✨ add animation to satellite movement * lint and prettier * make responsive --------- Co-authored-by: EliasKnudsen <elias.knudsen@gmail.com>
- Loading branch information
2 people
authored and
GitHub
committed
Apr 8, 2024
1 parent
7ad0821
commit 8f48f6a
Showing
9 changed files
with
517 additions
and
66 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| "use client"; | ||
| import React from "react"; | ||
| import { useSatelliteStore } from "@/lib/store"; | ||
|
|
||
| export default function SatelliteSelector() { | ||
| const satelliteNames = useSatelliteStore((state) => state.satelliteNames); | ||
| const selectedSatellite = useSatelliteStore( | ||
| (state) => state.selectedSatellite, | ||
| ); | ||
| const setSelectedSatellite = useSatelliteStore( | ||
| (state) => state.setSelectedSatellite, | ||
| ); | ||
| const fetchAndSetSatelliteData = useSatelliteStore( | ||
| (state) => state.fetchAndSetSatelliteData, | ||
| ); | ||
|
|
||
| const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||
| setSelectedSatellite(event.target.value); | ||
| }; | ||
|
|
||
| for (const satellite of satelliteNames) { | ||
| fetchAndSetSatelliteData(satellite); | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <select | ||
| className="block w-full cursor-pointer bg-black" | ||
| name="satellite" | ||
| id="satellite" | ||
| value={selectedSatellite} | ||
| onChange={handleChange} | ||
| > | ||
| {satelliteNames.map((satellite) => ( | ||
| <option | ||
| className="cursor-pointer" | ||
| key={satellite} | ||
| value={satellite} | ||
| > | ||
| {satellite} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </div> | ||
| ); | ||
| } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| "use client"; | ||
| import React, { useEffect, useRef } from "react"; | ||
| import Globe, { GlobeInstance } from "globe.gl"; | ||
| import * as THREE from "three"; | ||
| import { useSatelliteStore } from "@/lib/store"; | ||
| import { convertSatrec } from "@/lib/convertSatrec"; | ||
|
|
||
| const SAT_RADIUS = 5; // Relative size of the satellite for visualization | ||
| const UPDATE_INTERVAL_MS = 10; // Update interval in milliseconds | ||
| const EARTH_RADIUS_KM = 6371; // Earth radius in kilometers | ||
|
|
||
| export default function SatelliteGlobe() { | ||
| const chart = useRef<HTMLDivElement>(null); | ||
| const globeRef = useRef<GlobeInstance>(); | ||
| const { satelliteData, selectedSatellite, setSelectedSatellite } = | ||
| useSatelliteStore((state) => ({ | ||
| satelliteData: state.satelliteData, | ||
| selectedSatellite: state.selectedSatellite, | ||
| setSelectedSatellite: state.setSelectedSatellite, | ||
| })); | ||
|
|
||
| // Initialize the globe | ||
| useEffect(() => { | ||
| if (chart.current && !globeRef.current) { | ||
| globeRef.current = Globe()(chart.current) | ||
| .globeImageUrl( | ||
| "//unpkg.com/three-globe/example/img/earth-blue-marble.jpg", | ||
| )/*.backgroundImageUrl( | ||
| "//unpkg.com/three-globe/example/img/night-sky.png", | ||
| )*/ | ||
| .objectLat("lat") | ||
| .objectLng("lng") | ||
| .objectAltitude("alt") | ||
| .objectFacesSurface(false) | ||
| .backgroundColor("rgba(0,0,0,0)") | ||
| .objectLabel("name") | ||
| .objectsData([]) | ||
| .objectThreeObject((sat: any) => { | ||
| return new THREE.Mesh( | ||
| new THREE.SphereGeometry(SAT_RADIUS, 16, 8), | ||
| new THREE.MeshBasicMaterial({ color: sat.color }), | ||
| ); | ||
| }) | ||
| .onObjectClick((obj: any) => { | ||
| setSelectedSatellite(obj.name); | ||
| }); | ||
|
|
||
| // Set initial POV after globe instantiation | ||
| setTimeout(() => { | ||
| if (globeRef.current) { | ||
| globeRef.current.pointOfView({ altitude: 3.5 }); | ||
| } | ||
| }); | ||
|
|
||
| globeRef.current.controls().enabled = true; | ||
| globeRef.current.controls().enableZoom = false; | ||
|
|
||
| // Define the handleResize function | ||
| const handleResize = () => { | ||
| if (globeRef.current) { | ||
| if (window.innerWidth <= 768) { | ||
| globeRef.current.width(window.innerWidth); | ||
| globeRef.current.height(window.innerHeight); | ||
| } else { | ||
| globeRef.current.width(window.innerWidth); | ||
| globeRef.current.height(window.innerHeight); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // Handle the resize event | ||
| window.addEventListener("resize", handleResize); | ||
| handleResize(); // Call it initially to set the size | ||
|
|
||
| // Set initial positions of satellites | ||
| let currentDate = new Date().toISOString(); | ||
| const initialPositions = Object.values(satelliteData).map( | ||
| (sat) => ({ | ||
| lat: parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).latitudeDeg, | ||
| ), | ||
| lng: parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).longitudeDeg, | ||
| ), | ||
| alt: | ||
| parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).altitude, | ||
| ) / EARTH_RADIUS_KM, | ||
| name: sat.name, | ||
| }), | ||
| ); | ||
| globeRef.current.objectsData(initialPositions); | ||
|
|
||
| return () => { | ||
| window.removeEventListener("resize", handleResize); | ||
| }; | ||
| } | ||
| }, []); | ||
|
|
||
| // Update satellite positions periodically, or when satelliteData changes | ||
| useEffect(() => { | ||
| const intervalId = setInterval(() => { | ||
| const currentDate = new Date().toISOString(); | ||
|
|
||
| if (globeRef.current) { | ||
| const newPositions = Object.values(satelliteData).map((sat) => { | ||
| return { | ||
| lat: parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).latitudeDeg, | ||
| ), | ||
| lng: parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).longitudeDeg, | ||
| ), | ||
| alt: | ||
| parseFloat( | ||
| convertSatrec(sat.satrec, currentDate).altitude, | ||
| ) / EARTH_RADIUS_KM, | ||
| name: sat.name, | ||
| color: | ||
| selectedSatellite === sat.name | ||
| ? "red" | ||
| : "palegreen", | ||
| }; | ||
| }); | ||
|
|
||
| globeRef.current.objectsData(newPositions); | ||
| } | ||
| }, UPDATE_INTERVAL_MS); | ||
|
|
||
| if (satelliteData[selectedSatellite] === undefined) { | ||
| return; | ||
| } | ||
|
|
||
| const targetPosition = convertSatrec( | ||
| satelliteData[selectedSatellite].satrec, | ||
| new Date().toISOString(), | ||
| ); | ||
|
|
||
| globeRef?.current?.pointOfView( | ||
| { | ||
| lat: Number(targetPosition.latitudeDeg), | ||
| lng: Number(targetPosition.longitudeDeg), | ||
| altitude: 2.5, | ||
| }, | ||
| 1700, | ||
| ); | ||
|
|
||
| return () => clearInterval(intervalId); | ||
| }); | ||
|
|
||
| return <div id="chart" className="" ref={chart}></div>; | ||
| } |
93 changes: 93 additions & 0 deletions
93
frontend/src/components/satelliteData/SatelliteDataHome.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| "use client"; | ||
| import { useState, useEffect } from "react"; | ||
| import { convertSatrec, SatelliteInfo } from "@/lib/convertSatrec"; | ||
| import { useSatelliteStore } from "@/lib/store"; | ||
|
|
||
| const updateInterval = 10; | ||
|
|
||
| export default function SatelliteDataHome() { | ||
| const { satelliteData, fetchAndSetSatelliteData, selectedSatellite } = | ||
| useSatelliteStore(); | ||
| const [satelliteInfo, setSatelliteInfo] = useState<SatelliteInfo | null>( | ||
| null, | ||
| ); | ||
|
|
||
| // Fetch satellite data on component mount or when selectedSatellite changes | ||
| useEffect(() => { | ||
| if (selectedSatellite) { | ||
| fetchAndSetSatelliteData(selectedSatellite); | ||
| } | ||
| }, [fetchAndSetSatelliteData, selectedSatellite]); | ||
|
|
||
| // Update satellite info every `updateInterval` ms | ||
| useEffect(() => { | ||
| const intervalId = setInterval(() => { | ||
| if (selectedSatellite) { | ||
| // Access satellite data by name | ||
| const satData = satelliteData[selectedSatellite]; | ||
| if (satData) { | ||
| const updatedInfo = convertSatrec( | ||
| satData.satrec, | ||
| satData.name, | ||
| ); | ||
| setSatelliteInfo(updatedInfo); | ||
| } | ||
| } | ||
| }, updateInterval); | ||
|
|
||
| // Clear interval on component unmount | ||
| return () => clearInterval(intervalId); | ||
| }, [satelliteData, selectedSatellite]); | ||
|
|
||
| return ( | ||
| <div> | ||
| <div className="grid grid-cols-2 gap-0.5"> | ||
| <div className="bg-black p-5"> | ||
| <p className="text-xl font-medium"> | ||
| {satelliteInfo | ||
| ? satelliteInfo.velocity + " km/h" | ||
| : "Loading..."} | ||
| </p> | ||
| <p className="text-gray-400">Velocity</p> | ||
| </div> | ||
| <div className="bg-black p-5"> | ||
| <p className="text-xl font-medium"> | ||
| {satelliteInfo | ||
| ? satelliteInfo.altitude + " km" | ||
| : "Loading..."} | ||
| </p> | ||
| <p className="text-gray-400">Altitude</p> | ||
| </div> | ||
| <div className="bg-black p-5"> | ||
| <p className="text-xl font-medium"> | ||
| {satelliteInfo | ||
| ? satelliteInfo.latitudeDeg + "° N" | ||
| : "Loading..."} | ||
| </p> | ||
| <p className="text-gray-400">Latitude</p> | ||
| </div> | ||
| <div className="bg-black p-5"> | ||
| <p className="text-xl font-medium"> | ||
| {satelliteInfo | ||
| ? satelliteInfo.longitudeDeg + "° E" | ||
| : "Loading..."} | ||
| </p> | ||
| <p className="text-gray-400">Longitude</p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="mt-0.5 bg-black p-5"> | ||
| <div> | ||
| <p className="text-xl font-medium"> | ||
| {satelliteInfo | ||
| ? "Above " + satelliteInfo.country | ||
| : "Loading..."} | ||
| </p> | ||
| </div> | ||
| <div> | ||
| <p className="text-gray-400">Flag Icon</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.