From 7c5341487fc3f01561e03ffa843c017c195fd519 Mon Sep 17 00:00:00 2001 From: Lukas Thrane <76877975+Windove@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:37:25 +0200 Subject: [PATCH] 232 move 2d map to individual satellite page and integrate so specific satellite is shown (#253) * feat(frontend): add 2d map to individual satellite page * feat(frontend): Add 2d map to individual satellite page, add position prediction, style page * styling * prettier and lint --- frontend/next.config.mjs | 2 +- frontend/package-lock.json | 57 ++++++++++- frontend/package.json | 1 + frontend/src/app/layout.tsx | 4 +- .../app/satellites/[satelliteSlug]/page.tsx | 80 +++++++++++----- .../src/components/2dmap/2dMapProjection.tsx | 53 ++++++++--- frontend/src/components/2dmap/Map2d.tsx | 95 ++++++++++++++++--- .../components/homeComponents/SatDropdown.tsx | 2 +- .../satelliteData/SatelliteDataHome.tsx | 2 +- .../satelliteData/SatelliteDataIndividual.tsx | 93 ++++++++++++++++++ .../satelliteData/SatelliteStatsTable.tsx | 7 +- frontend/src/lib/convertSatrec.ts | 47 ++++++++- frontend/src/lib/store.ts | 7 ++ 13 files changed, 387 insertions(+), 63 deletions(-) create mode 100644 frontend/src/components/satelliteData/SatelliteDataIndividual.tsx diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 5d83319..fcfa1e7 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -13,7 +13,7 @@ const nextConfig = { { protocol: "http", hostname: "web.hypso.ies.ntnu.no", - port: '1337' + port: "1337", }, { protocol: "http", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 161349f..bc3f7a4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,6 +26,7 @@ "@turf/turf": "^6.5.0", "@visx/geo": "^3.5.0", "@visx/scale": "^3.5.0", + "@visx/shape": "^3.5.0", "add": "^2.0.6", "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", @@ -6375,6 +6376,11 @@ "@types/d3-color": "*" } }, + "node_modules/@types/d3-path": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz", + "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==" + }, "node_modules/@types/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", @@ -6383,6 +6389,14 @@ "@types/d3-time": "*" } }, + "node_modules/@types/d3-shape": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz", + "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==", + "dependencies": { + "@types/d3-path": "^1" + } + }, "node_modules/@types/d3-time": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", @@ -6452,6 +6466,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==" + }, "node_modules/@types/luxon": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", @@ -7934,6 +7953,15 @@ "dev": true, "peer": true }, + "node_modules/@visx/curve": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-3.3.0.tgz", + "integrity": "sha512-G1l1rzGWwIs8ka3mBhO/gj8uYK6XdU/3bwRSoiZ+MockMahQFPog0bUkuVgPwwzPSJfsA/E5u53Y/DNesnHQxg==", + "dependencies": { + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" + } + }, "node_modules/@visx/geo": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@visx/geo/-/geo-3.5.0.tgz", @@ -7971,6 +7999,28 @@ "@visx/vendor": "3.5.0" } }, + "node_modules/@visx/shape": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-3.5.0.tgz", + "integrity": "sha512-DP3t9jBQ7dSE3e6ptA1xO4QAIGxO55GrY/6P+S6YREuQGjZgq20TLYLAsiaoPEzFSS4tp0m12ZTPivWhU2VBTw==", + "dependencies": { + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/lodash": "^4.14.172", + "@types/react": "*", + "@visx/curve": "3.3.0", + "@visx/group": "3.3.0", + "@visx/scale": "3.5.0", + "classnames": "^2.3.1", + "d3-path": "^1.0.5", + "d3-shape": "^1.2.0", + "lodash": "^4.17.21", + "prop-types": "^15.5.10" + }, + "peerDependencies": { + "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0" + } + }, "node_modules/@visx/vendor": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-3.5.0.tgz", @@ -10302,8 +10352,7 @@ "node_modules/d3-path": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "peer": true + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "node_modules/d3-quadtree": { "version": "1.0.7", @@ -10342,7 +10391,6 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "peer": true, "dependencies": { "d3-path": "1" } @@ -14813,8 +14861,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", diff --git a/frontend/package.json b/frontend/package.json index 5fe8005..6e992ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,7 @@ "@turf/turf": "^6.5.0", "@visx/geo": "^3.5.0", "@visx/scale": "^3.5.0", + "@visx/shape": "^3.5.0", "add": "^2.0.6", "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index d05ed9c..de0d212 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -10,8 +10,8 @@ import { ApolloWrapper } from "@/components/wrappers/ApolloWrapper"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "NTNU SmallSat Lab", + description: "NTNU Small Satellite Lab", }; import ErrorBoundaryNavigation from "@/components/ErrorBoundaryNavigation"; diff --git a/frontend/src/app/satellites/[satelliteSlug]/page.tsx b/frontend/src/app/satellites/[satelliteSlug]/page.tsx index a2871a9..f90bfce 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/page.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/page.tsx @@ -3,9 +3,10 @@ import BlockRendererClient from "@/components/BlockRendererClient"; import fetchSatelliteInfo from "@/lib/data/fetchSatelliteInfo"; import { BlocksContent } from "@strapi/blocks-react-renderer"; import RelatedProjectsAndSatellites from "@/components/RelatedProjectsAndSatellites"; -import SatelliteDataHome from "@/components/satelliteData/SatelliteDataHome"; +import Map2d from "@/components/2dmap/Map2d"; import { useSatelliteStore } from "@/lib/store"; -import SolarDataComponent from "@/components/SolarActivity/SolarData"; +import SatelliteDataHome from "@/components/satelliteData/SatelliteDataHome"; + function setSelectedSatelliteSlug(satelliteSlug: string) { const setSelectedSatellite = useSatelliteStore.getState().setSelectedSatellite; @@ -39,31 +40,60 @@ export default async function SatelliteInfoPage({ if (!satelliteInfo) return
Loading...
; return ( - <> - +
+
+
+ {/* Container for satname, stats and sat image */} +
+ {/* Stats Container */} +
+
+

+ {satelliteInfo.name} +

+
+
+ +
+
+ + {/* Image container */} +
+
+

Satellite Image

+
+
+
+ + {/* Container for map */} +
+ +
+ + {/* Container for body content */} +
+ +
+
+ + {/* Related projects */} +
+ {satelliteInfo.relatedProjects?.length != 0 ? ( +

Related Projects

+ ) : null} -
-

- {satelliteInfo.name} -

- - - {satelliteInfo.relatedProjects?.length ? ( -

- Related Projects -

- ) : null} -
- {satelliteInfo.relatedProjects?.map( - (project: ProjectOrSatellite) => ( - - ), - )} +
+ {satelliteInfo.relatedProjects?.map( + (project: ProjectOrSatellite) => ( + + ), + )} +
- +
); } diff --git a/frontend/src/components/2dmap/2dMapProjection.tsx b/frontend/src/components/2dmap/2dMapProjection.tsx index f3ed08a..34cec70 100644 --- a/frontend/src/components/2dmap/2dMapProjection.tsx +++ b/frontend/src/components/2dmap/2dMapProjection.tsx @@ -1,7 +1,6 @@ -"use client"; -/* eslint-disable react/jsx-handler-names */ import React from "react"; import * as topojson from "topojson-client"; +// @ts-ignore import { CustomProjection } from "@visx/geo"; import { geoNaturalEarth1 } from "@visx/vendor/d3-geo"; import topology from "./world-topo.json"; @@ -11,6 +10,7 @@ export type GeoCustomProps = { height: number; satLatitude?: number; satLongitude?: number; + futurePositions?: [number, number][]; }; interface FeatureShape { @@ -20,35 +20,44 @@ interface FeatureShape { properties: { name: string }; } -export const background = ""; - // @ts-expect-error const world = topojson.feature(topology, topology.objects.units) as { type: "FeatureCollection"; features: FeatureShape[]; }; -export default function GeoCustom({ +export default function Map2dNaturalProjection({ width, height, satLatitude, satLongitude, + futurePositions, }: GeoCustomProps) { const centerX = width / 2; const centerY = height / 2; const scale = (width / 630) * 100; - // This function projects lat/long to the SVG coordinate system const projection = geoNaturalEarth1() .scale(scale) .translate([centerX, centerY]); - // Check if both satLatitude and satLongitude are defined let satPoint: [number, number] | undefined; if (typeof satLatitude === "number" && typeof satLongitude === "number") { satPoint = projection([satLongitude, satLatitude]) || undefined; } - return width < 10 ? null : ( + + // Function to interpolate the green color based on the index + const interPolateColor = (index: number, total: number) => { + const startColor = { r: 40, g: 96, b: 241 }; + const endColor = { r: 241, g: 0, b: 20 }; + const ratio = index / total; + const r = Math.round((1 - ratio) * startColor.r + ratio * endColor.r); + const g = Math.round((1 - ratio) * startColor.g + ratio * endColor.g); + const b = Math.round((1 - ratio) * startColor.b + ratio * endColor.b); + return `rgb(${r}, ${g}, ${b})`; + }; + + return ( <>
@@ -57,8 +66,7 @@ export default function GeoCustom({ y={0} width={width} height={height} - fill={background} - rx={14} + fill={"#000"} /> projection={geoNaturalEarth1} @@ -73,18 +81,37 @@ export default function GeoCustom({ ), )} + {futurePositions && + futurePositions.map(([lng, lat], i) => { + const point = projection([lng, lat]); + return ( + point && ( + + ) + ); + })} {satPoint && ( diff --git a/frontend/src/components/2dmap/Map2d.tsx b/frontend/src/components/2dmap/Map2d.tsx index f630af8..149cb9b 100644 --- a/frontend/src/components/2dmap/Map2d.tsx +++ b/frontend/src/components/2dmap/Map2d.tsx @@ -1,16 +1,27 @@ "use client"; -import GeoCustom from "./2dMapProjection"; +import Map2dNaturalProjection from "./2dMapProjection"; import { useSatelliteStore } from "@/lib/store"; -import React, { useState, useEffect } from "react"; -import { SatelliteInfo, convertSatrec } from "@/lib/convertSatrec"; +import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; +import { + SatelliteInfo, + convertSatrec, + predictFuturePositions, +} from "@/lib/convertSatrec"; -const updateInterval = 10; +const updateInterval = 50; export default function Map2d({ satName }: { satName: string }) { const { satelliteData, fetchAndSetSatelliteData } = useSatelliteStore(); const [satelliteInfo, setSatelliteInfo] = useState( null, ); + const [futurePositions, setFuturePositions] = useState<[number, number][]>( + [], + ); + const [projectionAmount, setProjectionAmount] = useState(120); + const [inputValue, setInputValue] = useState(120); + const containerRef = useRef(null); + const [size, setSize] = useState({ width: 0, height: 0 }); // Fetch satellite data on component mount useEffect(() => { @@ -32,7 +43,22 @@ export default function Map2d({ satName }: { satName: string }) { return () => clearInterval(intervalId); }, [satelliteData, satName]); - // Convert string values to numbers + // Calculate and update size based on the container's width + useLayoutEffect(() => { + function updateSize() { + if (containerRef.current) { + const width = containerRef.current.offsetWidth; + const height = width / 2; + setSize({ width, height }); + } + } + + window.addEventListener("resize", updateSize); + updateSize(); + + return () => window.removeEventListener("resize", updateSize); + }, []); + const satLatitude = satelliteInfo ? parseFloat(satelliteInfo.latitudeDeg) : undefined; @@ -40,16 +66,63 @@ export default function Map2d({ satName }: { satName: string }) { ? parseFloat(satelliteInfo.longitudeDeg) : undefined; - const width = 960; - const height = width / 2; + // Get future satellite positions on component mount + useEffect(() => { + if (!satelliteData[satName] || !satelliteData[satName].satrec) return; + + const predictions = predictFuturePositions( + satelliteData[satName].satrec, + projectionAmount, + ); + const futurePosTuples: [number, number][] = predictions.map( + (prediction) => [ + parseFloat(prediction.longitudeDeg), + parseFloat(prediction.latitudeDeg), + ], + ) as [number, number][]; + + setFuturePositions(futurePosTuples); + }, [satelliteData, satName, projectionAmount]); + + // Function to handle projection amount change + const handleInputChange = (event: { target: { value: any } }) => { + const value = event.target.value; + // Update the inputValue state + setInputValue(value); + + const newAmount = parseInt(value, 10); + if (!isNaN(newAmount)) { + // Update the projectionAmount only when newAmount is a number + setProjectionAmount(newAmount); + } + }; return ( -
- +
+

+ Current and Predicted Satellite Position +

+
+ +

+ Positions {projectionAmount} minutes into the{" "} + {projectionAmount >= 0 ? "future" : "past"} +

+
+
+
); diff --git a/frontend/src/components/homeComponents/SatDropdown.tsx b/frontend/src/components/homeComponents/SatDropdown.tsx index eea118d..dd668cf 100644 --- a/frontend/src/components/homeComponents/SatDropdown.tsx +++ b/frontend/src/components/homeComponents/SatDropdown.tsx @@ -25,7 +25,7 @@ export default function SatDropdown({ // Animation variants for the dropdown content const variants = { - open: { opacity: 1, height: "auto", maxHeight: "300px" }, + open: { opacity: 1, height: "auto", maxHeight: "250px" }, collapsed: { opacity: 0, height: 0 }, }; diff --git a/frontend/src/components/satelliteData/SatelliteDataHome.tsx b/frontend/src/components/satelliteData/SatelliteDataHome.tsx index f6c039f..644264f 100644 --- a/frontend/src/components/satelliteData/SatelliteDataHome.tsx +++ b/frontend/src/components/satelliteData/SatelliteDataHome.tsx @@ -46,7 +46,7 @@ export default function SatelliteDataHome() {

{satelliteInfo - ? satelliteInfo.velocity + " km/h" + ? satelliteInfo.velocity + " km/s" : "Loading..."}

Velocity

diff --git a/frontend/src/components/satelliteData/SatelliteDataIndividual.tsx b/frontend/src/components/satelliteData/SatelliteDataIndividual.tsx new file mode 100644 index 0000000..a706bff --- /dev/null +++ b/frontend/src/components/satelliteData/SatelliteDataIndividual.tsx @@ -0,0 +1,93 @@ +"use client"; +import { useState, useEffect } from "react"; +import { convertSatrec, SatelliteInfo } from "@/lib/convertSatrec"; +import { useSatelliteStore } from "@/lib/store"; + +const updateInterval = 50; + +export default function SatelliteDataIndividual() { + const { satelliteData, fetchAndSetSatelliteData, selectedSatellite } = + useSatelliteStore(); + const [satelliteInfo, setSatelliteInfo] = useState( + 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 ( +
+
+
+

+ {satelliteInfo + ? satelliteInfo.velocity + " km/s" + : "Loading..."} +

+

Velocity

+
+
+

+ {satelliteInfo + ? satelliteInfo.altitude + " km" + : "Loading..."} +

+

Altitude

+
+
+

+ {satelliteInfo + ? satelliteInfo.latitudeDeg + "° N" + : "Loading..."} +

+

Latitude

+
+
+

+ {satelliteInfo + ? satelliteInfo.longitudeDeg + "° E" + : "Loading..."} +

+

Longitude

+
+
+ +
+
+

+ {satelliteInfo + ? "Above " + satelliteInfo.country + : "Loading..."} +

+
+
+

Flag Icon

+
+
+
+ ); +} diff --git a/frontend/src/components/satelliteData/SatelliteStatsTable.tsx b/frontend/src/components/satelliteData/SatelliteStatsTable.tsx index 769eef3..894b63b 100644 --- a/frontend/src/components/satelliteData/SatelliteStatsTable.tsx +++ b/frontend/src/components/satelliteData/SatelliteStatsTable.tsx @@ -4,7 +4,7 @@ import { convertSatrec, SatelliteInfo } from "@/lib/convertSatrec"; import { useSatelliteStore } from "@/lib/store"; import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; -const updateInterval = 10; +const updateInterval = 50; export default function SatelliteStatsTable({ satName, @@ -46,6 +46,7 @@ export default function SatelliteStatsTable({
); } + return ( @@ -57,11 +58,11 @@ export default function SatelliteStatsTable({ -

{satelliteInfo.velocity + " Km/h"}

+

{satelliteInfo.velocity + " Km/s"}

{"Speed"}

-

{satelliteInfo.altitude + " Moh"}

+

{satelliteInfo.altitude + " km"}

{"Altitude"}

diff --git a/frontend/src/lib/convertSatrec.ts b/frontend/src/lib/convertSatrec.ts index 11aeef5..d8d9961 100644 --- a/frontend/src/lib/convertSatrec.ts +++ b/frontend/src/lib/convertSatrec.ts @@ -19,7 +19,12 @@ interface SatelliteInfo { country: string; } -export type { SatelliteInfo }; +interface SatelliteFutureInfo { + latitudeDeg: string; + longitudeDeg: string; +} + +export type { SatelliteInfo, SatelliteFutureInfo }; const findCountry = (latitudeDeg: number, longitudeDeg: number): string => { const pointFeature = point([longitudeDeg, latitudeDeg]); @@ -97,3 +102,43 @@ export const convertSatrec = ( country: country, }; }; + +// Function to predict the satellite's future positions +export const predictFuturePositions = ( + satrec: SatRec, + projectionAmount: number, +): SatelliteFutureInfo[] => { + const futurePositions: SatelliteFutureInfo[] = []; + const now = new Date(); + + // Predict the satellite's position every minute for the next projectionAmount minutes + const step = projectionAmount < 0 ? -1 : 1; + for ( + let i = 0; + step > 0 ? i <= projectionAmount : i >= projectionAmount; + i += step + ) { + const futureTime = new Date(now.getTime() + i * 60000); + + const positionAndVelocity = satellite.propagate(satrec, futureTime); + const gmst = satellite.gstime(futureTime); + const positionEci = positionAndVelocity.position; + + let positionGd; + if (positionEci && typeof positionEci !== "boolean") { + positionGd = satellite.eciToGeodetic(positionEci, gmst); + } + + if (positionGd && typeof positionGd !== "boolean") { + const latitudeDeg = satellite.degreesLat(positionGd.latitude); + const longitudeDeg = satellite.degreesLong(positionGd.longitude); + + futurePositions.push({ + longitudeDeg: longitudeDeg.toFixed(2), + latitudeDeg: latitudeDeg.toFixed(2), + }); + } + } + + return futurePositions; +}; diff --git a/frontend/src/lib/store.ts b/frontend/src/lib/store.ts index 072c4e0..dcf59fb 100644 --- a/frontend/src/lib/store.ts +++ b/frontend/src/lib/store.ts @@ -30,6 +30,13 @@ export const useSatelliteStore = create((set, get) => ({ "0 EXPLORER 11", "0 GREB", "0 METEOR 1-5", + "0 COSMOS 2486", + "0 YAOGAN 17B", + "0 POPACS 3", + "0 CINEMA 2", + "0 FIREBIRD A", + "0 EGYPTSAT 2", + "0 HODOYOSHI 3", ], selectedSatellite: "0 VANGUARD 2",