Skip to content

Commit

Permalink
152 install and set up zustand (#168)
Browse files Browse the repository at this point in the history
* feat(ide): ✨ Add zustand and store.ts

* feat(backend): ✨ Add fetch of satellite data based on name

* example usage

* semi-working country finder

* prettier

* lint

* simple styling for component

* feat(backend): ✨ Refactor code of zustand store to enable multiple satellite showings

* small fixes

* eslint and prettier
  • Loading branch information
Lukas Thrane authored and GitHub committed Apr 1, 2024
1 parent 2d4e5ae commit e51e6d8
Show file tree
Hide file tree
Showing 8 changed files with 575 additions and 225 deletions.
38 changes: 37 additions & 1 deletion frontend/package-lock.json

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

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"three": "^0.161.0",
"vaul": "^0.9.0"
"vaul": "^0.9.0",
"zustand": "^4.5.2"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.8.3",
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ export default async function Home() {

return (
<main>
<SatelliteDataTable />
<div className="grid grid-cols-2">
<SatelliteDataTable satName="HYPSO-1" />
<SatelliteDataTable satName="UME (ISS)" />
<SatelliteDataTable satName="STARLINK-1007" />
<SatelliteDataTable satName="VANGUARD 1" />
<SatelliteDataTable satName="MULTIFUNCTION TEST SAT" />
<SatelliteDataTable satName="huh" />
</div>

<SatelliteFetcher useExampleData={true} />

Expand Down
284 changes: 62 additions & 222 deletions frontend/src/components/satelliteData/SatelliteDataTable.tsx
Original file line number Diff line number Diff line change
@@ -1,235 +1,75 @@
// This component displays a table of satellite data including position and country
"use client";
import React, { useEffect, useState } from "react";
import { exampleData } from "../map/exampleSatData";
import { SatelliteData, mapRawDataToSatData } from "@/lib/mapHelpers";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import * as satellite from "satellite.js";
import { PolyUtil } from "node-geometry-library";
import globeData from "@components/map/githubglobe/files/globe-data.json";
import { useState, useEffect } from "react";
import { convertSatrec, SatelliteInfo } from "@/lib/convertSatrec";
import { useSatelliteStore } from "@/lib/store";

const satellitesShown = 10; // Maximum number of satellites to display
const timeInterval = 1000; // Time interval for updating satellite positions in milliseconds
const updateInterval = 10;

// Extends SatelliteData with calculated position properties
interface SatelliteDataWithPosition extends SatelliteData {
latitudeDeg: string;
longitudeDeg: string;
altitude: string;
velocity: string;
}

export default function SatelliteDataTable() {
const [satData, setSatData] = useState<SatelliteDataWithPosition[]>([]);
export default function SatelliteDataTable({ satName }: { satName: string }) {
const { satelliteData, fetchAndSetSatelliteData } = useSatelliteStore();
const [satelliteInfo, setSatelliteInfo] = useState<SatelliteInfo | null>(
null,
);

// Fetch satellite data on component mount
useEffect(() => {
// Updates satellite positions at specified intervals
const updateSatellitePositions = () => {
const updatedData = mapRawDataToSatData(exampleData)
.slice(0, satellitesShown)
.map((data) => {
const positionAndVelocity = satellite.propagate(
data.satrec,
new Date(),
);

if (
positionAndVelocity.position &&
typeof positionAndVelocity.position !== "boolean"
) {
const gmst = satellite.gstime(new Date()); // Calculates Greenwich Mean Sidereal Time
const positionGd = satellite.eciToGeodetic(
positionAndVelocity.position,
gmst,
);

// Extracts velocity from positionAndVelocity if it is not false
var velocityEci = positionAndVelocity.velocity;

if (typeof velocityEci !== "boolean") {
// Calculate the magnitude of the velocity vector if velocityEci is not false
var velocityMagnitude = Math.sqrt(
velocityEci.x * velocityEci.x +
velocityEci.y * velocityEci.y +
velocityEci.z * velocityEci.z,
);

// Convert velocity from kilometers per second (km/s) to kilometers per hour (km/h)
velocityMagnitude = velocityMagnitude * 3600;
} else {
// Set velocityMagnitude to NaN if velocityEci is false
velocityMagnitude = NaN;
}

// Converts geodetic position to readable format
const latitudeDeg = satellite.degreesLat(
positionGd.latitude,
);
const longitudeDeg = satellite.degreesLong(
positionGd.longitude,
);
const altitude = positionGd.height;

return {
...data,
latitudeDeg: latitudeDeg.toFixed(2),
longitudeDeg: longitudeDeg.toFixed(2),
altitude: altitude.toFixed(2),
velocity: velocityMagnitude.toFixed(0),
};
} else {
return {
...data,
latitudeDeg: "N/A",
longitudeDeg: "N/A",
altitude: "N/A",
velocity: "N/A",
};
}
});
fetchAndSetSatelliteData(satName);
}, [fetchAndSetSatelliteData, satName]);

setSatData(updatedData);
};

// Performs an initial update and sets the interval for further updates
updateSatellitePositions();
// Update satellite info every `updateInterval` ms
useEffect(() => {
const intervalId = setInterval(() => {
updateSatellitePositions();
}, timeInterval);

// Cleans up the interval when the component unmounts
// Access satellite data by name
const satData = satelliteData[satName];
if (satData) {
const updatedInfo = convertSatrec(satData.satrec, satData.name);
setSatelliteInfo(updatedInfo);
}
}, updateInterval);

// Clear interval on component unmount
return () => clearInterval(intervalId);
}, []);

return (
<div className="m-10 flex w-full flex-col items-center justify-center">
<Table className="w-1/2">
<TableCaption>Satellite Data</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-1/6">Satellite</TableHead>
<TableHead className="w-1/6">Latitude</TableHead>
<TableHead className="w-1/6">Longitude</TableHead>
<TableHead className="w-1/6">Altitude</TableHead>
<TableHead className="w-1/6">Velocity</TableHead>
<TableHead className="w-1/6">Country</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{satData.map((data, index) => {
let country = "Ocean"; // Default to Ocean if no country is found
globeData.features.forEach((countryFeature) => {
// Checks if the satellite is within a country's bounding box to reduce the number of polygons to check
const boundingBoxPoints = [
{
lat: countryFeature.bbox[1],
lng: countryFeature.bbox[0],
},
{
lat: countryFeature.bbox[3],
lng: countryFeature.bbox[0],
},
{
lat: countryFeature.bbox[3],
lng: countryFeature.bbox[2],
},
{
lat: countryFeature.bbox[1],
lng: countryFeature.bbox[2],
},
];

if (
PolyUtil.containsLocation(
{
lat: Number(data.latitudeDeg),
lng: Number(data.longitudeDeg),
},
boundingBoxPoints,
)
) {
// Handles polygons to accurately find the country
if (countryFeature.geometry.type == "Polygon") {
let boundingPolygon =
countryFeature.geometry.coordinates[0].map(
(coordinate) => ({
lat: Number(coordinate[1]),
lng: Number(coordinate[0]),
}),
);
}, [satelliteData, satName]);

if (
PolyUtil.containsLocation(
{
lat: Number(data.latitudeDeg),
lng: Number(data.longitudeDeg),
},
boundingPolygon,
)
) {
country =
countryFeature.properties.ADMIN;
}
} else if (
countryFeature.geometry.type ==
"MultiPolygon"
) {
// Loop through each polygon array in the MultiPolygon
const multiPolygon = countryFeature.geometry
.coordinates as number[][][][];
multiPolygon.forEach((polygon) => {
let boundingPolygon = polygon[0].map(
(coordinate) => ({
lat: Number(coordinate[1]),
lng: Number(coordinate[0]),
}),
);
// Display loading message if satellite info is not available
if (!satelliteInfo) {
return (
<div className="m-20">
<h1>Loading...</h1>
</div>
);
}

if (
PolyUtil.containsLocation(
{
lat: Number(
data.latitudeDeg,
),
lng: Number(
data.longitudeDeg,
),
},
boundingPolygon,
)
) {
country =
countryFeature.properties.ADMIN;
}
});
}
}
});

return (
<TableRow key={index}>
<TableCell>{data.name}</TableCell>
<TableCell>{data.latitudeDeg}° N</TableCell>
<TableCell>{data.longitudeDeg}° E</TableCell>
<TableCell>
{(Number(data.altitude) * 10).toFixed(0)}{" "}
moh
</TableCell>
<TableCell>{data.velocity} km/h</TableCell>
<TableCell>{country}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
return (
<div className="m-5 rounded-lg bg-gray-800 p-6 text-white shadow-lg">
<div className="mb-4 flex items-center justify-between">
<h1 className="text-4xl font-bold">{satelliteInfo.name}</h1>
<div>{/* Include the dropdown arrow icon here */}</div>
</div>

<div className="grid grid-cols-2 gap-4">
<div className="rounded bg-gray-700 p-4">
<p className="text-xl">{satelliteInfo.velocity} km/s</p>
<p className="text-gray-400">Velocity</p>
</div>
<div className="rounded bg-gray-700 p-4">
<p className="text-xl">{satelliteInfo.altitude} km</p>
<p className="text-gray-400">Altitude</p>
</div>
<div className="rounded bg-gray-700 p-4">
<p className="text-xl">{satelliteInfo.latitudeDeg}° N</p>
<p className="text-gray-400">Latitude</p>
</div>
<div className="ounded bg-gray-700 p-4">
<p className="text-xl">{satelliteInfo.longitudeDeg}° E</p>
<p className="text-gray-400">Longitude</p>
</div>
</div>

<div className="mt-4 rounded bg-gray-700 p-4">
<p className="text-xl">Above {satelliteInfo.country}</p>
</div>
<div className="ml-4">{/* Include the flag icon here */}</div>
</div>
);
}
Loading

0 comments on commit e51e6d8

Please sign in to comment.