Skip to content

Commit

Permalink
189 style satellitedatatable (#234)
Browse files Browse the repository at this point in the history
* 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
Show file tree
Hide file tree
Showing 9 changed files with 517 additions and 66 deletions.
6 changes: 3 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 21 additions & 7 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,35 @@ import ColoredSection from "@/components/ui/coloredSection";

import Image from "next/image";
import Link from "next/link";
import SatelliteDataTableMultiple from "@/components/satelliteData/SatelliteDataTableMultiple";
import fetchSatelliteData from "@components/map/SatelliteFetcher";

import fetchMostRecentImage from "@/lib/data/fetchMostRecentImage";

import SatelliteDataHome from "@/components/satelliteData/SatelliteDataHome";
import SatelliteSelector from "@/components/SatelliteSelector";
import SatelliteGlobe from "@/components/map/newGlobe";

export default async function Home() {
const mostRecentImageURL = await fetchMostRecentImage();

return (
<>
<div className="grid grid-cols-2">
<div className="grid grid-cols-2">
<SatelliteDataTableMultiple
fetchSatelliteData={fetchSatelliteData}
/>
<div className="flex flex-wrap bg-gray-600 p-0.5 md:flex-nowrap">
{/* Stats Container */}
<div className="flex w-full flex-col md:w-1/3">
<div className="bg-black p-5">
<SatelliteSelector />
</div>
<div className="mt-0.5">
<SatelliteDataHome />
</div>
<div className="mt-0.5 w-full bg-black md:flex-grow"></div>
</div>

{/* Globe Container */}
<div className="ml-0.5 w-full md:w-2/3">
<div className="h-full bg-black md:flex md:items-center md:justify-center">
<SatelliteGlobe />
</div>
</div>
</div>

Expand Down
46 changes: 46 additions & 0 deletions frontend/src/components/SatelliteSelector.tsx
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>
);
}
2 changes: 1 addition & 1 deletion frontend/src/components/map/MyGlobe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { SatelliteData } from "@/lib/mapHelpers";

const EARTH_RADIUS_KM = 6371; // km
const SAT_SIZE = 500; // km
const TIME_STEP = 1 * 1000; // per frame
const TIME_STEP = 1; // per frame
//const SATELLITE_AMOUNT = 100; // amount of satellites to display

export function mapRawDataToTleData(rawData: string): string[][] {
Expand Down
152 changes: 152 additions & 0 deletions frontend/src/components/map/newGlobe.tsx
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 frontend/src/components/satelliteData/SatelliteDataHome.tsx
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>
);
}
Loading

0 comments on commit 8f48f6a

Please sign in to comment.