Skip to content

Commit

Permalink
Fetching data from Space-Track, fixing some bugs, adding cron tasks f…
Browse files Browse the repository at this point in the history
…or updating database with tool functions
  • Loading branch information
Florian Graule committed Jul 9, 2024
1 parent 1e497fd commit 44a7505
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 7 deletions.
27 changes: 27 additions & 0 deletions backend/config/functions/cronTask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// backend/config/functions/cronTask.js
/*
* Function to fetch data from Space-Track such as Eccentricy, SMA, Inclination every month
* and update the database with the new data
*/
'use strict';

module.exports = {
updateAllSatellitesData: {
task: async ({ strapi }) => {
try {
// Fetching all satellites
const satellites = await strapi.entityService.findMany('api::satellite.satellite');

// Waiting for all promises to be resolved
await Promise.all(
satellites.map(async satellite => {
await strapi.service('api::satellite.satellite').fetchOrbitalData(satellite.id);
})
);
} catch (error) {
console.error(error);
}
},
options: new Date(Date.now() + 10000),
},
};
12 changes: 11 additions & 1 deletion backend/config/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ module.exports = [
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
'strapi::body',
{
name: "strapi::body",
config: {
formLimit: "256mb", // modify form body
jsonLimit: "256mb", // modify JSON body
textLimit: "256mb", // modify text body
formidable: {
maxFileSize: 200 * 1024 * 1024, // multipart data, modify here limit of uploaded file size
},
},
},
'strapi::session',
'strapi::favicon',
'strapi::public',
Expand Down
7 changes: 7 additions & 0 deletions backend/config/server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const cronTask = require('./functions/cronTask');

module.exports = ({ env }) => ({
host: env("HOST", "127.0.0.1"),
port: env.int("PORT", 1337),
Expand All @@ -7,4 +9,9 @@ module.exports = ({ env }) => ({
webhooks: {
populateRelations: env.bool("WEBHOOKS_POPULATE_RELATIONS", false),
},
cron: {
// Enable or disable the cron tasks
enabled: env.bool("CRON_ENABLED", false),
tasks: cronTask,
},
});
3 changes: 3 additions & 0 deletions backend/src/api/satellite/content-types/satellite/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
},
"massKg": {
"type": "float"
},
"historicalOrbitalData": {
"type": "json"
}
}
}
15 changes: 14 additions & 1 deletion backend/src/api/satellite/controllers/satellite.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,17 @@

const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::satellite.satellite');
module.exports = createCoreController('api::satellite.satellite', ({ strapi }) => ({
async findOne(ctx) {
// Fetching data from Space-Track
const entity = await strapi.entityService.findOne('api::satellite.satellite', ctx.params.id);
const updatedSatellite = await strapi.service('api::satellite.satellite').fetchOrbitalData(ctx.params.id);

if (!entity.historicalOrbitalData) {
return updatedSatellite;
}

return entity;
}
})
);
66 changes: 65 additions & 1 deletion backend/src/api/satellite/services/satellite.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,70 @@
* satellite service
*/


const { createCoreService } = require('@strapi/strapi').factories;

module.exports = createCoreService('api::satellite.satellite');
const axios = require('axios');

// Function to fetch data from Space-Track such as Eccentricy, SMA, Inclination
async function fetchOrbitalData(contextId) {
try {
// Fetching the satellite
const satellite = await strapi.entityService.findOne('api::satellite.satellite', contextId);
const noradId = satellite.catalogNumberNORAD;

// Authentification to Space-Track
const authResponse = await axios.post('https://www.space-track.org/ajaxauth/login', {
identity: 'grauleflorian@gmail.com',
password : 'Vm5JxTtD3-hYBdq'
});

if (authResponse.status === 200) {
// Fetching data from Space-Track
const satelliteResponse = await axios.get(`https://www.space-track.org/basicspacedata/query/class/gp_history/NORAD_CAT_ID/${noradId}/orderby/TLE_LINE1%20ASC/EPOCH/1950-07-02--2024-07-02/format/json`, {
headers: {
Cookie: authResponse.headers['set-cookie']
}
});

if (satelliteResponse.status === 200) {
// Collecting data
const satelliteData = satelliteResponse.data;
// Parsing correctly the data with wanted data : Inclination, Eccentricity, SMA, and Epoch
const historicalOrbitalData = satelliteData.map(data => {
return {
epoch: data.EPOCH,
inclination: data.INCLINATION,
eccentricity: data.ECCENTRICITY,
semiMajorAxis: data.SEMIMAJOR_AXIS
}
});

if (satellite) {
// Updating the satellite with the new data
const updatedSatellite = await strapi.entityService.update('api::satellite.satellite', contextId, {
data: {
historicalOrbitalData: historicalOrbitalData,
},
});
return updatedSatellite;
} else {
throw new Error('Satellite not found while updating orbit data');
}


} else {
throw new Error('Error while fetching data from Space-Track');
}
} else {
throw new Error('Authentication failed');
}
} catch (error) {
console.error('Error while fetching data to Space-Track: ', error);
}
}

module.exports = {
...createCoreService('api::satellite.satellite'),
fetchOrbitalData,
};
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@visx/shape": "^3.5.0",
"@visx/vendor": "^3.5.0",
"add": "^2.0.6",
"chart.js": "^4.4.1",
"chart.js": "^4.4.3",
"chartjs-adapter-luxon": "^1.3.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand All @@ -59,6 +59,7 @@
"playwright": "^1.43.1",
"qs": "^6.11.2",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-markdown": "^9.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use client";

import React, { useState, useEffect, useRef } from 'react';

interface ScrollBarThumbProps {
scrollBarThumbWidth: number;
svgContainerRect: {topLeft: number, width: number, height: number};
}

const ScrollBarThumb : React.FC<ScrollBarThumbProps> = ({ scrollBarThumbWidth, svgContainerRect}) => {
const isDragging = useRef<boolean>(false);
{/* SB is used for ScrollBar */}
const [sBThumbX, setSBThumbX] = useState(0);
const thumbRef = useRef<SVGRectElement>(null);
// Distance between the left of the thumb and the mouse click
const distThumbClick = useRef<number | null>(null);

/* Be careful useEffect runs before parent props are received */
useEffect(() => {
const handleMouseUp = (e: any) => {
if (thumbRef.current) {
isDragging.current = false;
}
}

const handleMouseMove = (e: MouseEvent) => {
if (isDragging.current && thumbRef.current) {
// Scrollbar starts at the right of the svg container and goes to the left by increasing SBThumbX
setSBThumbX(() => {
// Calculating min and max x positions following the SBThumbX axis for moving the thumb with mouse movement
const minX= 0.5;
const maxX = svgContainerRect.width - scrollBarThumbWidth;
// newPos represents left border of the thumb
const newPos = svgContainerRect.topLeft + svgContainerRect.width - scrollBarThumbWidth - (e.clientX - (distThumbClick.current? distThumbClick.current : 0));

// If mouse movement isn't in the scrollable area
if (newPos <= minX) {
return minX;
} else if (newPos >= maxX) {
return maxX;
}
return newPos;
});
}
}

window.addEventListener('mouseup', handleMouseUp);
window.addEventListener('mousemove', handleMouseMove);

// Managing the resize of the thumb
if (thumbRef.current && thumbRef.current.getBoundingClientRect().x < svgContainerRect.topLeft) {
setSBThumbX(svgContainerRect.width - scrollBarThumbWidth);
}

return () => {
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('mousemove', handleMouseMove);
}
}, [scrollBarThumbWidth, svgContainerRect]);

const handleMouseDown = (e: React.MouseEvent<SVGRectElement, MouseEvent>) => {
// If the thumb is clicked with left mouse button, we start dragging
if (thumbRef.current && e.button === 0) {
isDragging.current = true;
distThumbClick.current = e.clientX - thumbRef.current.getBoundingClientRect().left;
}
}

return (
<>
<g className="scrollbar-thumb" transform={`translate(${20 + svgContainerRect.width - scrollBarThumbWidth - sBThumbX}, 0)`}>
<rect x="0.5" y="-50.5" width={scrollBarThumbWidth} height="50" fill="#cccccc" opacity="0.1"/>
<rect ref={thumbRef} x="0.5" y="0.5" width={scrollBarThumbWidth} height={(svgContainerRect.height-1)} fill="#cccccc" style={{cursor: 'pointer'}} onMouseDown={handleMouseDown}/>
<path d={`M ${scrollBarThumbWidth/2} ${(svgContainerRect.height-1)*0.25} L ${scrollBarThumbWidth/2} ${(svgContainerRect.height-1)*0.75}
M ${scrollBarThumbWidth*0.5 - svgContainerRect.width*0.005} ${(svgContainerRect.height-1)*0.25} L ${scrollBarThumbWidth*0.5 - svgContainerRect.width*0.005} ${(svgContainerRect.height-1)*0.75}
M ${scrollBarThumbWidth*0.5 + svgContainerRect.width*0.005} ${(svgContainerRect.height-1)*0.25} L ${scrollBarThumbWidth*0.5 + svgContainerRect.width*0.005} ${(svgContainerRect.height-1)*0.75}`}
fill="none" stroke="#333333" style={{cursor: 'pointer'}} onMouseDown={handleMouseDown}/>
</g>
</>
);
}

export default ScrollBarThumb;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import React, { useState, useEffect } from "react";

type LaunchDateCountDownProps = {
export type LaunchDateCountDownProps = {
launchDate: string | Date | undefined;
};

Expand Down
121 changes: 121 additions & 0 deletions frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import React, { useState, useLayoutEffect, useRef, SyntheticEvent } from 'react'
import { SatelliteNumber } from '@/lib/store'
import { Line } from 'react-chartjs-2'
import Chart from 'chart.js'
import { LaunchDateCountDownProps } from './launchDateCountDown';
import ScrollBarThumb from './_orbitDataGraphComponents/ScrollBarThumb';

type OrbitDataProps = {
satNum : SatelliteNumber;
launchDateString: LaunchDateCountDownProps['launchDate'];
orbitalData: any;
}

const OrbitDataGraph : React.FC<OrbitDataProps> = ({ satNum, launchDateString, orbitalData }) => {

// href for the svg component, tracking the size of the container
const svgContainer = useRef<HTMLDivElement>(null);
const [svgSize, setSvgSize] = useState({width: 0, height: 0});
{/* SB use for ScrollBar*/}
const [scrollBarThumbWidth, setSBThumbWidth] = useState(0);

// Handling button for zooming in and out of the graph on a time scale
const launchDate = launchDateString? new Date(launchDateString) : new Date();
const calculateMonthsDiff = () => {
const currentDate = new Date();
return currentDate.getMonth() - launchDate.getMonth() + (12 * (currentDate.getFullYear() - launchDate.getFullYear()));
}

const months = calculateMonthsDiff();

const handleZoomClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// We round the width of the scrollbar thumb at one decimal
const period = e.currentTarget.textContent;
const regExp = new RegExp('^([1234567890]+)([my])$');
const match = period?.match(regExp);
let SBThumbWidth = 0;

if (period === "All") {
SBThumbWidth = svgSize.width*0.8-40.5;
setSBThumbWidth(SBThumbWidth);
} else {
const number = match ? parseInt(match[1]) : 0;
const periodType = match? match[2] : "";

if (periodType === "m") {
SBThumbWidth = Math.round((svgSize.width*0.8 * number *10) / months) / 10;
setSBThumbWidth(SBThumbWidth);
} else if (periodType === "y") {
SBThumbWidth = Math.round((svgSize.width*0.8 * number * 12 *10) / months) / 10
setSBThumbWidth(SBThumbWidth);
}
}
}

// Layout effect to track the size of the container and update the svg size
useLayoutEffect(() => {
const updateSize = () => {
if (svgContainer.current) {
const width = svgContainer.current.offsetWidth;
const height = width / 2;
setSvgSize({width, height});
setSBThumbWidth( width*0.1 );
}
}
window.addEventListener('resize', updateSize);
updateSize();

return () => window.removeEventListener('resize', updateSize);
}, []);

return (
<>
<h1>
Graph goes here.
</h1>
<div ref={svgContainer} className="w-full flex flex-col">
<div className="zoom-container flex">
<h2 className="mx-5 text-white">Zoom : </h2>
{/* Scrollbar thumb represents the zoom period selected, in case it fits bad we don't display
ie. containerSize > SBThumbWidth > 20px */}
{ (Math.round((svgSize.width*0.8 * 1 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 1 * 10) / months) / 10 > 20) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">1m</button> }
{ (Math.round((svgSize.width*0.8 * 3 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 3 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">3m</button> }
{ (Math.round((svgSize.width*0.8 * 6 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 6 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">6m</button> }
{ (Math.round((svgSize.width*0.8 * 12 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">1y</button> }
{ (Math.round((svgSize.width*0.8 * 12 * 5 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 5 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">5y</button> }
{ (Math.round((svgSize.width*0.8 * 12 * 10 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 10 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">10y</button> }
{ (Math.round((svgSize.width*0.8 * 12 * 20 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 20 * 10) / months) / 10 > 40) && <button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">20y</button> }

<button onClick={handleZoomClick} className="mx-2 bg-[#f2f2f2] text-black w-10 h-8 rounded-lg hover:bg-[#cccccc]">All</button>
</div>
<div className="w-full flex justify-center items-center">
<svg className="relative" width="100%" height="600">
{/* Scrollbar for time navigation */}
<g transform={`translate(${svgSize.width*0.1}, ${svgSize.height-75})`} fill="#f2f2f2">
<g>
<rect y="-50" width="80%" height="1"/>
</g>
<rect width="80%" height="20"/>
{/* Scrollbar left navigation arrow */}
<g>
<rect x="0.5" y="0.5" width="19" height="19" fill="#e6e6e6" stroke="#cccccc" strokeWidth="1"/>
<path d="M 13 5 L 6 10 L 13 15" fill="#333333"/>
</g>
{/* Scrollbar right navigation arrow */}
<g transform={`translate(${svgSize.width*0.8 - 20}, 0)`}>
<rect x="0.5" y="0.5" width="19" height="19" fill="#e6e6e6" stroke="#cccccc" strokeWidth="1"/>
<path d="M 7 5 L 14 10 L 7 15" fill="#333333"/>
</g>
{/* Scrollbar thumb */}
<ScrollBarThumb scrollBarThumbWidth={scrollBarThumbWidth} svgContainerRect={{topLeft: (svgContainer.current?.getBoundingClientRect()?(svgContainer.current?.getBoundingClientRect().x) : 0) + svgSize.width*0.1 + 20.5 , width: (svgSize.width*0.8-40.5), height: 20}}/>
</g>
</svg>
</div>
</div>
</>
)
}

export default OrbitDataGraph;
Loading

0 comments on commit 44a7505

Please sign in to comment.