8 | {/*
23 | );
24 | }
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/migrations/0003_remove_collision_conjunction_id_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.3 on 2024-12-07 01:21
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('api', '0002_user'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='collision',
16 | name='conjunction_id',
17 | ),
18 | migrations.RemoveField(
19 | model_name='probabilitycalc',
20 | name='conjuction_id',
21 | ),
22 | migrations.AddField(
23 | model_name='collision',
24 | name='cdm',
25 | field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='collisions', to='api.cdm'),
26 | preserve_default=False,
27 | ),
28 | migrations.AddField(
29 | model_name='probabilitycalc',
30 | name='cdm',
31 | field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='probability_calcs', to='api.cdm'),
32 | preserve_default=False,
33 | ),
34 | migrations.DeleteModel(
35 | name='Conjunction',
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/on-orbit-frontend/src/components/csa/csa.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 |
3 | const Csa = () => {
4 | return(
5 |
6 |
7 |
Canadian Space Agency
8 |
9 |
10 |
Contact the
11 | CSA
12 |
13 |
14 |
15 |
About the
16 | CSA
17 |
18 |
19 |
20 |
Notices and copyright
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default Csa
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/serializers/organization_serializer.py:
--------------------------------------------------------------------------------
1 | # organization_serializer.py
2 | from django.contrib.auth import get_user_model
3 | from rest_framework import serializers
4 | from ..models import Organization
5 | from .cdm_serializer import CDMSerializer # Adjust the import as needed
6 | from ..permissions import IsAdmin
7 |
8 | User = get_user_model()
9 |
10 | class OrganizationSerializer(serializers.ModelSerializer):
11 | users = serializers.PrimaryKeyRelatedField(
12 | many=True,
13 | queryset=User.objects.all()
14 | )
15 | cdms = CDMSerializer(many=True, read_only=True)
16 |
17 | def update(self, instance, validated_data):
18 | request = self.context.get("request", None)
19 | # Only admin (staff) users can override the alert_threshold or users
20 | if ('alert_threshold' in validated_data) or ('users' in validated_data):
21 | admin_permission = IsAdmin()
22 | if not admin_permission.has_permission(request, None):
23 | # If the request user fails the admin check, raise a ValidationError
24 | raise serializers.ValidationError({
25 | "detail": "Only admin users can modify alert_threshold or manage users."
26 | })
27 |
28 | return super().update(instance, validated_data)
29 |
30 | class Meta:
31 | model = Organization
32 | fields = ('id', 'name', 'alert_threshold', 'users', 'cdms')
33 |
--------------------------------------------------------------------------------
/on-orbit-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "on-orbit-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emotion/react": "^11.13.3",
13 | "@emotion/styled": "^11.13.0",
14 | "@mui/material": "^6.1.6",
15 | "@supabase/supabase-js": "^2.48.1",
16 | "@types/d3": "^7.4.3",
17 | "@types/topojson-client": "^3.1.5",
18 | "cesium": "^1.126.0",
19 | "chart.js": "^4.4.8",
20 | "chartjs-adapter-date-fns": "^3.0.0",
21 | "class-variance-authority": "^0.7.1",
22 | "clsx": "^2.1.1",
23 | "d3": "^7.9.0",
24 | "d3-geo": "^3.1.1",
25 | "daisyui": "^4.12.20",
26 | "highcharts": "^12.1.2",
27 | "highcharts-react-official": "^3.2.1",
28 | "lucide-react": "^0.486.0",
29 | "next": "15.0.2",
30 | "react": "^18.3.1",
31 | "react-chartjs-2": "^5.3.0",
32 | "react-dom": "^18.3.1",
33 | "resium": "^1.19.0-beta.1",
34 | "satellite.js": "^5.0.0",
35 | "tailwind-merge": "^3.0.2",
36 | "tailwindcss-animate": "^1.0.7",
37 | "topojson-client": "^3.1.0"
38 | },
39 | "devDependencies": {
40 | "@types/node": "^20",
41 | "@types/react": "^18",
42 | "@types/react-dom": "^18",
43 | "eslint": "^8",
44 | "eslint-config-next": "15.0.2",
45 | "postcss": "^8",
46 | "tailwindcss": "^3.4.1",
47 | "typescript": "^5"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/permissions.py:
--------------------------------------------------------------------------------
1 | # api/permissions.py
2 |
3 | from rest_framework import permissions
4 |
5 |
6 | class IsAdmin(permissions.BasePermission):
7 | """
8 | Allows access only to admin users.
9 | """
10 | def has_permission(self, request, view):
11 | return bool(request.user and request.user.role == 'admin')
12 |
13 |
14 | class IsCollisionAnalyst(permissions.BasePermission):
15 | """
16 | Allows access only to collision analysts.
17 | """
18 | def has_permission(self, request, view):
19 | return bool(request.user and request.user.role == 'collision_analyst')
20 |
21 |
22 | class IsUser(permissions.BasePermission):
23 | """
24 | Allows access only to regular users.
25 | """
26 | def has_permission(self, request, view):
27 | return bool(request.user and request.user.role == 'user')
28 |
29 |
30 | class CanViewCDM(permissions.BasePermission):
31 | """
32 | Custom permission to allow users to view CDMs based on their role and CDM privacy.
33 | - Admins and Collision Analysts can view all CDMs.
34 | - Regular Users can only view CDMs where privacy is True.
35 | """
36 | def has_object_permission(self, request, view, obj):
37 | if request.user.role in ['admin', 'collision_analyst']:
38 | return True
39 | return obj.privacy
40 |
41 | def has_permission(self, request, view):
42 | # Allow access only if the user is authenticated
43 | return bool(request.user and request.user.is_authenticated)
44 |
--------------------------------------------------------------------------------
/on-orbit-frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/views/organization_views.py:
--------------------------------------------------------------------------------
1 | # organization_views.py
2 | from rest_framework import viewsets
3 | from rest_framework.decorators import action
4 | from rest_framework.permissions import IsAuthenticated
5 | from rest_framework.filters import SearchFilter
6 | from rest_framework.response import Response
7 |
8 | from ..models import Organization
9 | from ..serializers import OrganizationSerializer, UserSerializer, CDMSerializer
10 |
11 | class OrganizationViewSet(viewsets.ModelViewSet):
12 | """
13 | A viewset that provides the standard actions for the Organization model,
14 | plus custom endpoints to list users and CDMs.
15 | """
16 | queryset = Organization.objects.all()
17 | serializer_class = OrganizationSerializer
18 | permission_classes = [IsAuthenticated]
19 |
20 | filter_backends = [SearchFilter]
21 | search_fields = ['name']
22 |
23 | @action(detail=True, methods=['get'], url_path='users')
24 | def get_users(self, request, pk=None):
25 | """
26 | Returns all users belonging to this organization.
27 | """
28 | organization = self.get_object() # Retrieve the Organization instance
29 | serializer = UserSerializer(organization.users.all(), many=True)
30 | return Response(serializer.data)
31 |
32 | @action(detail=True, methods=['get'], url_path='cdms')
33 | def get_cdms(self, request, pk=None):
34 | """
35 | Returns all CDMs associated with this organization.
36 | """
37 | organization = self.get_object()
38 | serializer = CDMSerializer(organization.cdms.all(), many=True)
39 | return Response(serializer.data)
40 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/models/organization.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.conf import settings
3 | from django.db.models import Q
4 | from .cdm import CDM
5 |
6 | class Organization(models.Model):
7 | name = models.CharField(max_length=255, unique=True)
8 | alert_threshold = models.FloatField(default=1.0e-9)
9 | users = models.ManyToManyField(
10 | settings.AUTH_USER_MODEL,
11 | related_name='organizations',
12 | blank=True
13 | )
14 | cdms = models.ManyToManyField(
15 | CDM,
16 | related_name='organizations',
17 | blank=True
18 | )
19 |
20 | def save(self, *args, **kwargs):
21 | """
22 | When saving an Organization, automatically link any CDMs where this org's name
23 | matches sat1_operator_organization or sat2_operator_organization.
24 | """
25 | super().save(*args, **kwargs)
26 |
27 | # Find all CDMs where either sat1_operator_organization or sat2_operator_organization
28 | # matches this organization's name.
29 | matching_cdms = CDM.objects.filter(
30 | Q(sat1_operator_organization=self.name) | Q(sat2_operator_organization=self.name)
31 | )
32 | self.cdms.set(matching_cdms)
33 |
34 | def notify_if_collision_exceeds_threshold(self, collision):
35 | # TODO: Implement this method
36 | if collision.probability_of_collision >= self.alert_threshold:
37 | if collision.cdm in self.cdms.all():
38 | self.notify_users(collision)
39 |
40 | def notify_users(self, collision):
41 | # TODO: Implement this method
42 | for user in self.users.all():
43 | print(f"Notifying {user.username} about collision {collision}")
44 |
45 | def __str__(self):
46 | return self.name
47 |
--------------------------------------------------------------------------------
/on-orbit-frontend/src/components/about/about.tsx:
--------------------------------------------------------------------------------
1 | const About = () => {
2 | return (
3 |
4 |
5 |
About Us
6 |
7 |
8 | Welcome to the On-Orbit Collision Predictor. This project is a McGill Capstone project in collaboration with the Canadian Space Agency. Our goal is to provide accurate data on satellites in orbit and predict the probability of collisions to help ensure the safety and sustainability of space operations.
9 |
10 |
11 | Our team consists of dedicated students and professionals who are passionate about space and technology. We leverage advanced algorithms and data analysis techniques to deliver reliable predictions and insights.
12 |
13 |
14 | Thank you for visiting our site. If you have any questions or would like to learn more about our project, please feel free to contact us.
15 |
16 |
17 |
18 |
19 |
Team
20 |
21 |
Erik
22 |
Masa
23 |
Wasif
24 |
Yassine
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | export default About
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/authentication.py:
--------------------------------------------------------------------------------
1 | # api/authentication.py
2 |
3 | import jwt
4 | from django.conf import settings
5 | from rest_framework import authentication, exceptions
6 | from .models import User
7 |
8 |
9 | class JWTAuthentication(authentication.BaseAuthentication):
10 | """
11 | Custom JWT Authentication class.
12 | """
13 |
14 | def authenticate(self, request):
15 | auth_header = authentication.get_authorization_header(request)
16 |
17 | if not auth_header:
18 | return None # No authentication credentials provided
19 |
20 | try:
21 | prefix, token = auth_header.decode('utf-8').split(' ')
22 | if prefix.lower() != 'bearer':
23 | return None # Invalid prefix
24 | except ValueError:
25 | raise exceptions.AuthenticationFailed('Invalid token header. No credentials provided.')
26 |
27 | return self.authenticate_credentials(token)
28 |
29 | def authenticate_credentials(self, token):
30 | try:
31 | payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=['HS256'])
32 | except jwt.ExpiredSignatureError:
33 | raise exceptions.AuthenticationFailed('Token has expired.')
34 | except jwt.InvalidTokenError:
35 | raise exceptions.AuthenticationFailed('Invalid token.')
36 |
37 | user_id = payload.get('user_id')
38 | role = payload.get('role')
39 |
40 | try:
41 | user = User.objects.get(id=user_id)
42 | except User.DoesNotExist:
43 | raise exceptions.AuthenticationFailed('User not found.')
44 |
45 | # Optionally, you can verify the role matches
46 | if user.role != role:
47 | raise exceptions.AuthenticationFailed('Invalid token payload.')
48 |
49 | return (user, token)
50 |
--------------------------------------------------------------------------------
/on-orbit-frontend/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | theme: {
11 | extend: {
12 | colors: {
13 | background: 'hsl(var(--background))',
14 | foreground: 'hsl(var(--foreground))',
15 | card: {
16 | DEFAULT: 'hsl(var(--card))',
17 | foreground: 'hsl(var(--card-foreground))'
18 | },
19 | popover: {
20 | DEFAULT: 'hsl(var(--popover))',
21 | foreground: 'hsl(var(--popover-foreground))'
22 | },
23 | primary: {
24 | DEFAULT: 'hsl(var(--primary))',
25 | foreground: 'hsl(var(--primary-foreground))'
26 | },
27 | secondary: {
28 | DEFAULT: 'hsl(var(--secondary))',
29 | foreground: 'hsl(var(--secondary-foreground))'
30 | },
31 | muted: {
32 | DEFAULT: 'hsl(var(--muted))',
33 | foreground: 'hsl(var(--muted-foreground))'
34 | },
35 | accent: {
36 | DEFAULT: 'hsl(var(--accent))',
37 | foreground: 'hsl(var(--accent-foreground))'
38 | },
39 | destructive: {
40 | DEFAULT: 'hsl(var(--destructive))',
41 | foreground: 'hsl(var(--destructive-foreground))'
42 | },
43 | border: 'hsl(var(--border))',
44 | input: 'hsl(var(--input))',
45 | ring: 'hsl(var(--ring))',
46 | chart: {
47 | '1': 'hsl(var(--chart-1))',
48 | '2': 'hsl(var(--chart-2))',
49 | '3': 'hsl(var(--chart-3))',
50 | '4': 'hsl(var(--chart-4))',
51 | '5': 'hsl(var(--chart-5))'
52 | }
53 | },
54 | borderRadius: {
55 | lg: 'var(--radius)',
56 | md: 'calc(var(--radius) - 2px)',
57 | sm: 'calc(var(--radius) - 4px)'
58 | }
59 | }
60 | },
61 | plugins: [require("daisyui"), require("tailwindcss-animate")],
62 | };
63 | export default config;
64 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/Product3x3_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = ...
2 | {matlab.unittest.fixtures.PathFixture('..')}) ...
3 | Product3x3_UnitTest < matlab.unittest.TestCase
4 | % Product3x3_UnitTest - Unit test for Product3x3
5 | %
6 | % =========================================================================
7 | %
8 | % Copyright (c) 2023 United States Government as represented by the
9 | % Administrator of the National Aeronautics and Space Administration.
10 | % All Rights Reserved.
11 | %
12 | % =========================================================================
13 | %
14 | % Initial version: Jul 2023; Latest update: Jul 2023
15 | %
16 | % ----------------- BEGIN CODE -----------------
17 |
18 | methods (Test)
19 | % Tests 1000 random 3x3 matrices
20 | function testDiagonal (testCase)
21 | A = 2000 * rand(1000, 9) - 1000;
22 | B = 2000 * rand(1000, 9) - 1000;
23 |
24 | P = Product3x3(A, B);
25 |
26 | for i = 1:1000
27 | M = reshape(P(i, :), 3, 3)';
28 | T = reshape(A(i, :), 3, 3)' * reshape(B(i, :), 3, 3)';
29 |
30 | testCase.verifyEqual(M, T);
31 | end
32 | end
33 | end
34 |
35 | end
36 |
37 | % ----------------- END OF CODE -----------------
38 | %
39 | % Please record any changes to the software in the change history
40 | % shown below:
41 | %
42 | %---------------- CHANGE HISTORY ------------------
43 | % Developer | Date | Description
44 | %--------------------------------------------------
45 | % E. White | 07-03-2023 | Initial development
46 |
47 | % =========================================================================
48 | %
49 | % Copyright (c) 2023 United States Government as represented by the
50 | % Administrator of the National Aeronautics and Space Administration.
51 | % All Rights Reserved.
52 | %
53 | % =========================================================================
54 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/EquinoctialMatrices.m:
--------------------------------------------------------------------------------
1 | function [X,P,E,J,K,Q,QRemStat,QRaw,QRem,CRem] = EquinoctialMatrices(r,v,C,RemEqCov)
2 |
3 | % Calculate equinoctial state, covariance and Jacobian matrices
4 |
5 | % Cartestian state vector and covariance matrix
6 | X = [r; v]/1e3; % Pos/vel state vec. in km units
7 | P = C/1e6; % Pos/vel covariance in km units
8 |
9 | % Calculate the equinoctial state vector and associated matrices
10 | try
11 | % Calculate equinoctial elements from the pos-vel state
12 | [~,n,af,ag,chi,psi,lM,~] = ...
13 | convert_cartesian_to_equinoctial(X(1:3),X(4:6));
14 | lM = mod(lM,2*pi);
15 | % Equinoctial state vector, E
16 | E = [n,af,ag,chi,psi,lM]';
17 | % Jacobian matrix, J = dX/dE
18 | J = jacobian_equinoctial_to_cartesian(E,X);
19 | % Inverse of Jacobian matrix, K = dE/dX
20 | K = J \ eye(6,6);
21 | % Equinoctial state covariance, Q = K * P * K'
22 | Q = cov_make_symmetric(K * P * K');
23 | % Save raw covariance
24 | QRaw = Q;
25 | % Remediate eq. covariance, if required
26 | if RemEqCov
27 | % Calc Q remediation status and remediated Q matrix
28 | [~,~,~,~,QRemStat,~,~,QRem] = ...
29 | CovRemEigValClip(Q);
30 | if QRemStat
31 | Q = cov_make_symmetric(QRem);
32 | P = cov_make_symmetric(J * Q * J');
33 | CRem = 1e6*P;
34 | else
35 | CRem = C;
36 | end
37 | else
38 | % Calc Q remediation status only
39 | [~,~,~,~,QRemStat] = CovRemEigValClip(Q);
40 | QRem = Q;
41 | CRem = C;
42 | end
43 | catch
44 | warning('EquinoctialMatrices:CalculationFailure','Equinoctial state/covariance/Jacobian calculation failure');
45 | E = NaN(6,1);
46 | J = NaN(6,6);
47 | K = NaN(6,6);
48 | Q = NaN(6,6);
49 | QRemStat = NaN(1,1);
50 | QRaw = NaN(6,6);
51 | QRem = NaN(6,6);
52 | if RemEqCov
53 | CRem = NaN(6,6);
54 | else
55 | CRem = C;
56 | end
57 | end
58 |
59 | return
60 | end
61 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import include, path
2 | from rest_framework.routers import DefaultRouter
3 | from .views import (
4 | CollisionListCreateView, CollisionDetailView, UserViewSet,
5 | ProbabilityCalcListCreateView, ProbabilityCalcDetailView,
6 | CDMSerializerListCreateView, CDMCalcDetailView, RegisterView, LoginView, CDMViewSet, RefreshTokenView, CDMCreateView, OrganizationViewSet,
7 | CollisionTradespaceView, CollisionLinearTradespaceView, CurrentUserView, CDMPrivacyToggleView, UserNotificationToggleView
8 | )
9 |
10 | router = DefaultRouter()
11 | router.register(r'cdms', CDMViewSet, basename='cdm')
12 | router.register(r'organizations', OrganizationViewSet, basename='organization')
13 | router.register(r'users', UserViewSet, basename='user')
14 |
15 | urlpatterns = [
16 | path('collisions/', CollisionListCreateView.as_view(), name='collision-list-create'),
17 | path('collisions/
/', CollisionDetailView.as_view(), name='collision-detail'),
18 | path('probabilities/', ProbabilityCalcListCreateView.as_view(), name='probability-list-create'),
19 | path('probabilities//', ProbabilityCalcDetailView.as_view(), name='probability-detail'),
20 | # path('cdms/', CDMSerializerListCreateView.as_view(), name='cdm-list-create'),
21 | path('cdms//', CDMCalcDetailView.as_view(), name='cdm-detail'),
22 | path('cdms/create/', CDMCreateView.as_view(), name='cdm-create'),
23 | path('cdms//privacy/', CDMPrivacyToggleView.as_view(), name='cdm-privacy-toggle'),
24 | path('tradespace/', CollisionTradespaceView.as_view(), name='collision-tradespace'),
25 | path('tradespace/linear/', CollisionLinearTradespaceView.as_view(), name='collision-linear-tradespace'),
26 | path('register/', RegisterView.as_view(), name='register'),
27 | path('login/', LoginView.as_view(), name='login'),
28 | path('refresh/', RefreshTokenView.as_view(), name='refresh_token'),
29 | path('users/current_user/', CurrentUserView.as_view(), name='current_user'),
30 | path('users/notifications/', UserNotificationToggleView.as_view(), name='user-notification-toggle'),
31 | path('', include(router.urls)),
32 | ]
33 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/PeakOverlapMD2.m:
--------------------------------------------------------------------------------
1 | function [MD2,Xu,Ps,Asdet,Asinv,POPconverged] = PeakOverlapMD2(t,t10,Eb10,Qb10,t20,Eb20,Qb20,HBR,EMD2,params)
2 |
3 | % Calculate the effective Maha. distance using the peak overlap point.
4 | %
5 | % Actually, calculate MD^2 + log(det(As)), to account for slight variations
6 | % in the determinant of the As matrix, which shows up in the denominator.
7 |
8 | % Find the peak overlap position (POP) at the nominal TCA
9 |
10 | % Calc pos/vel mean states and associated Jacobians at specified time
11 | [Jb1,xb1] = jacobian_E0_to_Xt(t,Eb10);
12 | [Jb2,xb2] = jacobian_E0_to_Xt(t,Eb20);
13 |
14 | % Calc peak overlap position
15 | % params.verbose = true;
16 | [POPconverged,~,~,~,POP] = PeakOverlapPos(t, ...
17 | xb1,Jb1,t10,Eb10,Qb10, ...
18 | xb2,Jb2,t20,Eb20,Qb20, ...
19 | HBR,params);
20 |
21 | if POPconverged > 0
22 |
23 | % Relative POP-corrected distance
24 | Xu = POP.xu2-POP.xu1; ru = Xu(1:3);
25 |
26 | % Construct the POP-corrected joint pos/vel covariance
27 | if params.XCprocessing
28 | % DCP 6x1 sensitivity vectors for cartesian pos/vel
29 | % state at the current eph time - see Hall (2021) eq 57
30 | GCp = POP.Js1*params.GEp;
31 | GCs = POP.Js2*params.GEs;
32 | Ps = POP.Ps1 + POP.Ps2 - params.sigpXsigs * (GCs*GCp'+GCp*GCs');
33 | else
34 | Ps = POP.Ps1 + POP.Ps2;
35 | end
36 |
37 | % Calculate the inverse of A, remediating NPDs with eigenvalue clipping
38 | As = Ps(1:3,1:3);
39 | % Lclip = (params.Fclip*HBR)^2;
40 | Lclip = 0; % Required for one Obj 48901 DCP case
41 | % Slower version
42 | % [~,~,~,~,~,Asdet,Asinv] = CovRemEigValClip(As,Lclip);
43 | % Faster version
44 | [Veig,Leig] = eig(As); Leig = diag(Leig); Leig(Leig < Lclip) = Lclip;
45 | Asdet = Leig(1) * Leig(2) * Leig(3);
46 | Asinv = Veig * diag(1./Leig) * Veig';
47 |
48 | % Calculate effective Maha distance
49 | MD2 = ru' * Asinv * ru;
50 |
51 | % Calculate the "effective" or modified Maha distance,
52 | % by adding ln(|As|), or ln(|As|) - 2*ln(|vu])
53 | if EMD2 == 1
54 | MD2 = MD2 + log(Asdet);
55 | elseif EMD2 == 2
56 | MD2 = MD2 + log(Asdet) - 2*log(norm(Xu(4:6)));
57 | end
58 |
59 | else
60 |
61 | % Return null values for no POP convergence
62 | MD2 = NaN; Xu = []; Ps = []; Asdet = []; Asinv = [];
63 |
64 | end
65 |
66 | return
67 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/Product3x3.m:
--------------------------------------------------------------------------------
1 | function [Out] = Product3x3(a,b)
2 | % Product3x3 - vectorized 3x3 matrix multiplication routine
3 | %
4 | % Syntax: [Out] = Product3x3(a,b);
5 | %
6 | % =========================================================================
7 | %
8 | % Copyright (c) 2023 United States Government as represented by the
9 | % Administrator of the National Aeronautics and Space Administration.
10 | % All Rights Reserved.
11 | %
12 | % =========================================================================
13 | %
14 | % Input:
15 | %
16 | % a - Left matrix [nx9]
17 | % b - Right matrix [nx9]
18 | %
19 | % =========================================================================
20 | %
21 | % Output:
22 | %
23 | % Out - Matrix product [nx9]
24 | %
25 | % =========================================================================
26 | %
27 | % Notes:
28 | %
29 | % Inputs and outputs are formatted as [col1, col2, col3]:
30 | % [(1,1) (2,1) (3,1) (1,2) (2,2) (3,2) (1,3) (2,3) (3,3)]
31 | %
32 | % =========================================================================
33 | %
34 | % Initial version: May 2022; Latest update: Jul 2023
35 | %
36 | % ----------------- BEGIN CODE -----------------
37 |
38 | Out = [a(:,1).*b(:,1)+a(:,2).*b(:,4)+a(:,3).*b(:,7) ...
39 | a(:,1).*b(:,2)+a(:,2).*b(:,5)+a(:,3).*b(:,8) ...
40 | a(:,1).*b(:,3)+a(:,2).*b(:,6)+a(:,3).*b(:,9) ...
41 | a(:,4).*b(:,1)+a(:,5).*b(:,4)+a(:,6).*b(:,7) ...
42 | a(:,4).*b(:,2)+a(:,5).*b(:,5)+a(:,6).*b(:,8) ...
43 | a(:,4).*b(:,3)+a(:,5).*b(:,6)+a(:,6).*b(:,9) ...
44 | a(:,7).*b(:,1)+a(:,8).*b(:,4)+a(:,9).*b(:,7) ...
45 | a(:,7).*b(:,2)+a(:,8).*b(:,5)+a(:,9).*b(:,8) ...
46 | a(:,7).*b(:,3)+a(:,8).*b(:,6)+a(:,9).*b(:,9)];
47 | end
48 |
49 | % ----------------- END OF CODE -----------------
50 | %
51 | % Please record any changes to the software in the change history
52 | % shown below:
53 | %
54 | % ----------------- CHANGE HISTORY ------------------
55 | % Developer | Date | Description
56 | % ---------------------------------------------------
57 | % L. Baars | 05-19-2022 | Initial development
58 | % E. White | 07-12-2023 | Added compliant documentation
59 |
60 | % =========================================================================
61 | %
62 | % Copyright (c) 2023 United States Government as represented by the
63 | % Administrator of the National Aeronautics and Space Administration.
64 | % All Rights Reserved.
65 | %
66 | % =========================================================================
67 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/models/collision.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from django.db import models
3 | from .cdm import CDM
4 | import matlab.engine
5 |
6 | class Collision(models.Model):
7 | cdm = models.ForeignKey(CDM, on_delete=models.CASCADE, related_name='collisions')
8 | probability_of_collision = models.FloatField()
9 | sat1_object_designator = models.CharField(max_length=50)
10 | sat2_object_designator = models.CharField(max_length=50)
11 |
12 | @classmethod
13 | def create_from_cdm(cls, cdm):
14 | if not cdm:
15 | raise ValueError("A valid CDM object must be provided.")
16 | matlabPathFile = Path(__file__).resolve().parent.parent / 'matlab'
17 |
18 | eng = matlab.engine.start_matlab()
19 | eng.addpath(str(matlabPathFile))
20 | r1 = matlab.double([cdm.sat1_x, cdm.sat1_y, cdm.sat1_z])
21 | v1 = matlab.double([cdm.sat1_x_dot, cdm.sat1_y_dot, cdm.sat1_z_dot])
22 | cov1 = matlab.double([
23 | [cdm.sat1_cov_rr, cdm.sat1_cov_rt, cdm.sat1_cov_rn],
24 | [cdm.sat1_cov_tr, cdm.sat1_cov_tt, cdm.sat1_cov_tn],
25 | [cdm.sat1_cov_nr, cdm.sat1_cov_nt, cdm.sat1_cov_nn]
26 | ])
27 |
28 | r2 = matlab.double([cdm.sat2_x, cdm.sat2_y, cdm.sat2_z])
29 | v2 = matlab.double([cdm.sat2_x_dot, cdm.sat2_y_dot, cdm.sat2_z_dot])
30 | cov2 = matlab.double([
31 | [cdm.sat2_cov_rr, cdm.sat2_cov_rt, cdm.sat2_cov_rn],
32 | [cdm.sat2_cov_tr, cdm.sat2_cov_tt, cdm.sat2_cov_tn],
33 | [cdm.sat2_cov_nr, cdm.sat2_cov_nt, cdm.sat2_cov_nn]
34 | ])
35 | HBR = cdm.hard_body_radius
36 | RelTol = 1e-08
37 | HBRType = 'circle'
38 | probability_of_collision_matlab = eng.Pc2D_Foster(r1, v1, cov1, r2, v2, cov2, HBR, RelTol, HBRType, nargout=1)
39 | probability_of_collision = float(probability_of_collision_matlab)
40 |
41 | if probability_of_collision > 1.0:
42 | probability_of_collision = 1.0
43 |
44 | return cls.objects.create(
45 | cdm=cdm,
46 | probability_of_collision=probability_of_collision,
47 | sat1_object_designator=cdm.sat1_object_designator,
48 | sat2_object_designator=cdm.sat2_object_designator,
49 | )
50 |
51 | def save(self, *args, **kwargs):
52 | # Ensure satellite IDs are copied from the related CDM
53 | if self.cdm:
54 | self.sat1_object_designator = self.cdm.sat1_object_designator
55 | self.sat2_object_designator = self.cdm.sat2_object_designator
56 | super().save(*args, **kwargs)
57 |
58 | def __str__(self):
59 | return f"Collision {self.id} between {self.sat1_object_designator} and {self.sat2_object_designator}"
60 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/migrations/0004_remove_user_password_hash_cdm_privacy_user_groups_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.3 on 2024-12-07 02:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('api', '0003_remove_collision_conjunction_id_and_more'),
10 | ('auth', '0012_alter_user_first_name_max_length'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='user',
16 | name='password_hash',
17 | ),
18 | migrations.AddField(
19 | model_name='cdm',
20 | name='privacy',
21 | field=models.BooleanField(default=True),
22 | ),
23 | migrations.AddField(
24 | model_name='user',
25 | name='groups',
26 | field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'),
27 | ),
28 | migrations.AddField(
29 | model_name='user',
30 | name='is_active',
31 | field=models.BooleanField(default=True),
32 | ),
33 | migrations.AddField(
34 | model_name='user',
35 | name='is_staff',
36 | field=models.BooleanField(default=False),
37 | ),
38 | migrations.AddField(
39 | model_name='user',
40 | name='is_superuser',
41 | field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'),
42 | ),
43 | migrations.AddField(
44 | model_name='user',
45 | name='last_login',
46 | field=models.DateTimeField(blank=True, null=True, verbose_name='last login'),
47 | ),
48 | migrations.AddField(
49 | model_name='user',
50 | name='password',
51 | field=models.CharField(default='password', max_length=128, verbose_name='password'),
52 | preserve_default=False,
53 | ),
54 | migrations.AddField(
55 | model_name='user',
56 | name='role',
57 | field=models.CharField(choices=[('admin', 'Administrator'), ('collision_analyst', 'Collision Analyst'), ('user', 'User')], default='user', max_length=50),
58 | ),
59 | migrations.AddField(
60 | model_name='user',
61 | name='user_permissions',
62 | field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
63 | ),
64 | ]
65 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/GetAlfanoTestCases.m:
--------------------------------------------------------------------------------
1 | function conj = GetAlfanoTestCases(params)
2 | %==========================================================================
3 | %
4 | % Use the Alfano (2009) test case Excel files to a conjunction list in
5 | % the form output by function OCMDB_get_conjunctions.
6 | %
7 | % REFERENCE:
8 | %
9 | % S.Alfano, "Satellite Conjunction Monte Carlo Analysis" AAS 09-233 (2009).
10 | %
11 | %==========================================================================
12 |
13 | % Initializations and defaults
14 |
15 | if (nargin == 0); params = []; end
16 |
17 | if ~isfield(params,'verbose'); params.verbose = []; end
18 | if isempty(params.verbose); params.verbose = 0; end
19 |
20 | if ~isfield(params,'case_list'); params.case_list = []; end
21 | if isempty(params.case_list); params.case_list = (1:12); end
22 |
23 | if ~isfield(params,'data_path'); params.data_path = []; end
24 | if isempty(params.data_path); params.data_path = ...
25 | 'Alfano_2009_Test_Cases'; end
26 |
27 | % Number of A09 conjunctions to retrieve
28 |
29 | Nconj = numel(params.case_list);
30 |
31 | % Load the database. This creates a structure called "DB".
32 |
33 | if params.verbose
34 | disp(' ');
35 | disp(['Loading ' num2str(Nconj) ' Alfano (2009) conjunction test cases from path:']);
36 | disp([' ' params.data_path]);
37 | end
38 |
39 | % Allocate the conjunction arrays
40 | conj.case = NaN(1,Nconj); % Test case number
41 | conj.X1 = NaN(6,Nconj); % ECI state
42 | conj.C1 = NaN(6,6,Nconj); % ECI covariance
43 | conj.X2 = NaN(6,Nconj); % ECI state
44 | conj.C2 = NaN(6,6,Nconj); % ECI covariance
45 | conj.HBR = NaN(1,Nconj); % Combined hard-body radii
46 |
47 | % Loop through conjunctions
48 |
49 | for nc = 1:Nconj
50 |
51 | % Get this A09 test case
52 | conj.case(nc) = params.case_list(nc);
53 | if params.verbose
54 | disp([' Loading case number ' num2str(conj.case(nc)) ' from Excel files']);
55 | end
56 | tc = get_alfano_test_case(conj.case(nc),params.data_path);
57 |
58 | % Combined HBR
59 | conj.HBR(nc) = tc.HBR;
60 |
61 | % Primary object ECI state
62 | X1 = [tc.R1o' tc.V1o'];
63 |
64 | % Primary object ECI covariance
65 | C1ECI = tc.P1o;
66 | % C1ECI = cov_make_symmetric(C1ECI);
67 |
68 | % Secondary object ECI state
69 | X2 = [tc.R2o' tc.V2o'];
70 |
71 | % Secondary object ECI covariance
72 | C2ECI = tc.P2o;
73 | % C2ECI = cov_make_symmetric(C2ECI);
74 |
75 | % Define the outputs in MKS units
76 | conj.X1(:,nc) = X1;
77 | conj.X2(:,nc) = X2;
78 | conj.C1(:,:,nc) = C1ECI;
79 | conj.C2(:,:,nc) = C2ECI;
80 |
81 | end
82 |
83 | return;
84 | end
85 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/models/user.py:
--------------------------------------------------------------------------------
1 | # api/models/user.py
2 | import bcrypt
3 | from django.db import models
4 | import uuid
5 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
6 |
7 |
8 | class UserManager(BaseUserManager):
9 | def create_user(self, email, password=None, role='user', **extra_fields):
10 | """
11 | Creates and saves a User with the given email, password, and role.
12 | """
13 | if not email:
14 | raise ValueError('The Email field must be set')
15 | email = self.normalize_email(email)
16 | user = self.model(email=email, role=role, **extra_fields)
17 | if password:
18 | user.set_password(password)
19 | else:
20 | raise ValueError('The Password field must be set')
21 | user.save(using=self._db)
22 | return user
23 |
24 | def create_superuser(self, email, password=None, role='admin', **extra_fields):
25 | """
26 | Creates and saves a superuser with the given email, password, and role.
27 | """
28 | extra_fields.setdefault('is_staff', True)
29 | extra_fields.setdefault('is_superuser', True)
30 |
31 | if role != 'admin':
32 | raise ValueError('Superuser must have role=admin.')
33 | if not extra_fields.get('is_staff'):
34 | raise ValueError('Superuser must have is_staff=True.')
35 | if not extra_fields.get('is_superuser'):
36 | raise ValueError('Superuser must have is_superuser=True.')
37 |
38 | return self.create_user(email, password, role, **extra_fields)
39 |
40 |
41 | class User(AbstractBaseUser, PermissionsMixin):
42 | ROLE_CHOICES = (
43 | ('admin', 'Administrator'),
44 | ('collision_analyst', 'Collision Analyst'),
45 | ('user', 'User'),
46 | )
47 |
48 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
49 | email = models.EmailField(unique=True)
50 | role = models.CharField(max_length=50, choices=ROLE_CHOICES, default='user')
51 | is_active = models.BooleanField(default=True)
52 | is_staff = models.BooleanField(default=False) # Required for admin access
53 | created_at = models.DateTimeField(auto_now_add=True)
54 |
55 | notifications = models.BooleanField(default=True)
56 |
57 | interested_cdms = models.ManyToManyField(
58 | 'CDM',
59 | related_name='interested_users',
60 | blank=True
61 | )
62 |
63 | objects = UserManager()
64 |
65 | USERNAME_FIELD = 'email'
66 | REQUIRED_FIELDS = []
67 |
68 | def __str__(self):
69 | return self.email
70 |
71 | # Override set_password and check_password to use bcrypt
72 | def set_password(self, raw_password):
73 | salt = bcrypt.gensalt()
74 | hashed = bcrypt.hashpw(raw_password.encode('utf-8'), salt)
75 | self.password = hashed.decode('utf-8')
76 |
77 | def check_password(self, raw_password):
78 | return bcrypt.checkpw(raw_password.encode('utf-8'), self.password.encode('utf-8'))
79 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/CheckAndResizeCov_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = { ...
2 | matlab.unittest.fixtures.PathFixture('..')}) ...
3 | CheckAndResizeCov_UnitTest < matlab.unittest.TestCase
4 |
5 | properties (TestParameter)
6 | numR = {1; 1};
7 | cov = {[1 2 3; 4 5 6; 7 8 9];
8 | [9 8 7; 6 5 4; 3 2 1]};
9 | expCov = {[1 2 3 4 5 6 7 8 9];
10 | [9 8 7 6 5 4 3 2 1]};
11 | end
12 |
13 | methods (Test)
14 | function test_nx9_valid_input(testCase)
15 | % Tests if an nx9 with the right number of rows is successfully
16 | % accepted
17 | numR = 2;
18 | cov = [1 2 3 4 5 6 7 8 9;
19 | 2 3 4 5 6 7 8 9 10];
20 | expCov = cov;
21 |
22 | actCov = CheckAndResizeCov(numR, cov);
23 |
24 | testCase.verifyEqual(actCov, expCov);
25 | end
26 |
27 | function test_nx9_num_rows_mismatch(testCase)
28 | % Tests if the function returns an error if there is a mismatch
29 | % between the "n" in the nx9 and number of rows
30 | numR = 1;
31 | cov = [1 2 3 4 5 6 7 8 9;
32 | 2 3 4 5 6 7 8 9 10];
33 |
34 | verifyError(testCase,@() CheckAndResizeCov(numR, cov),'CheckAndResizeCov:RowCountMismatch2D');
35 | end
36 |
37 | function test_3x3(testCase)
38 | % Tests if the function properly reformats a 3x3 into a 1x9
39 | numR = {1,2};
40 | cov = {[1 2 3;
41 | 4 5 6;
42 | 7 8 9], ...
43 | [9 8 7;
44 | 6 5 4;
45 | 3 2 1]};
46 | expCov = {[1 2 3 4 5 6 7 8 9], ...
47 | [9 8 7 6 5 4 3 2 1;
48 | 9 8 7 6 5 4 3 2 1]};
49 |
50 | for i = 1:length(numR)
51 | actCov = CheckAndResizeCov(numR{i}, cov{i});
52 |
53 | testCase.verifyEqual(actCov, expCov{i});
54 | end
55 | end
56 |
57 | function test_6x6(testCase)
58 | % Test if the function properly reformats a 6x6 into a 1x9
59 | numR = {1,2};
60 | cov = {[1 2 3 4 5 6;
61 | 2 7 8 9 0 1;
62 | 3 8 2 3 4 5;
63 | 4 9 3 6 7 8;
64 | 5 0 4 7 9 0;
65 | 6 1 5 8 0 1], ...
66 | [9 8 7 6 5 4;
67 | 8 3 2 1 0 9;
68 | 7 2 8 7 6 5;
69 | 6 1 7 4 3 2;
70 | 5 0 6 3 1 0;
71 | 4 9 5 2 0 9]};
72 | expCov = {[1 2 3 2 7 8 3 8 2], ...
73 | [9 8 7 8 3 2 7 2 8;
74 | 9 8 7 8 3 2 7 2 8]};
75 |
76 | for i = 1:length(numR)
77 | actCov = CheckAndResizeCov(numR{i}, cov{i});
78 |
79 | testCase.verifyEqual(actCov, expCov{i});
80 | end
81 | end
82 | end
83 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/migrations/0006_cdm_sat1_catalog_name_cdm_sat1_covariance_method_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.3 on 2025-02-03 02:45
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('api', '0005_merge_20250203_0240'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='cdm',
15 | name='sat1_catalog_name',
16 | field=models.CharField(blank=True, max_length=100, null=True),
17 | ),
18 | migrations.AddField(
19 | model_name='cdm',
20 | name='sat1_covariance_method',
21 | field=models.CharField(blank=True, max_length=100, null=True),
22 | ),
23 | migrations.AddField(
24 | model_name='cdm',
25 | name='sat1_international_designator',
26 | field=models.CharField(blank=True, max_length=100, null=True),
27 | ),
28 | migrations.AddField(
29 | model_name='cdm',
30 | name='sat1_object_name',
31 | field=models.CharField(blank=True, max_length=100, null=True),
32 | ),
33 | migrations.AddField(
34 | model_name='cdm',
35 | name='sat1_object_type',
36 | field=models.CharField(blank=True, max_length=100, null=True),
37 | ),
38 | migrations.AddField(
39 | model_name='cdm',
40 | name='sat1_operator_organization',
41 | field=models.CharField(blank=True, max_length=100, null=True),
42 | ),
43 | migrations.AddField(
44 | model_name='cdm',
45 | name='sat1_reference_frame',
46 | field=models.CharField(blank=True, max_length=100, null=True),
47 | ),
48 | migrations.AddField(
49 | model_name='cdm',
50 | name='sat2_catalog_name',
51 | field=models.CharField(blank=True, max_length=100, null=True),
52 | ),
53 | migrations.AddField(
54 | model_name='cdm',
55 | name='sat2_covariance_method',
56 | field=models.CharField(blank=True, max_length=100, null=True),
57 | ),
58 | migrations.AddField(
59 | model_name='cdm',
60 | name='sat2_international_designator',
61 | field=models.CharField(blank=True, max_length=100, null=True),
62 | ),
63 | migrations.AddField(
64 | model_name='cdm',
65 | name='sat2_object_name',
66 | field=models.CharField(blank=True, max_length=100, null=True),
67 | ),
68 | migrations.AddField(
69 | model_name='cdm',
70 | name='sat2_object_type',
71 | field=models.CharField(blank=True, max_length=100, null=True),
72 | ),
73 | migrations.AddField(
74 | model_name='cdm',
75 | name='sat2_operator_organization',
76 | field=models.CharField(blank=True, max_length=100, null=True),
77 | ),
78 | migrations.AddField(
79 | model_name='cdm',
80 | name='sat2_reference_frame',
81 | field=models.CharField(blank=True, max_length=100, null=True),
82 | ),
83 | ]
84 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/CheckAndResizeCov.m:
--------------------------------------------------------------------------------
1 | function [cov] = CheckAndResizeCov(numR, cov)
2 | % CheckAndResizeCov - Resizes the covariance passed in into an n x 9
3 | % matrix to be used in vectorized covariance
4 | % processing.
5 | %
6 | % Description:
7 | %
8 | % Reformats the input covariance into an n x 9 matrix representing the
9 | % 3x3 position covariance in the following format:
10 | % cov = [C(1,1) C(1,2) C(1,3) C(2,1) C(2,2) C(2,3) C(3,1) C(3,2) C(3,3)]
11 | % Where "n" is the number of rows passed in.
12 | %
13 | % Depending on the input format, this function will respond in several
14 | % different ways to reformat the cov matrix:
15 | % - If the input is an nx9, then this function verifies that n matches
16 | % numR.
17 | % - If the input is a 3x3, then this function will reformat the matrix
18 | % into a 1x9 and then repeat this vector numR times to create n rows.
19 | % - If the input is a 6x6, then this function will take the upper left
20 | % 3x3 component and reformat it into a 1x9. Then it will repeat this
21 | % vector numR times to create n rows.
22 | % - If the input is a 3x3xn, then this function will verify that n
23 | % matches numR and will reformat each 3x3 into a corresponding 1x9.
24 | % - If the input is a 6x6xn, then this function will verify that n
25 | % matches and will take the upper left 3x3 component of each 6x6 into a
26 | % corresponding 1x9.
27 | % - Any other input is considered an error.
28 |
29 | covSize = size(cov);
30 | % If a 2D covariance matrix was passed in
31 | if size(covSize,2) == 2
32 | if covSize(2) ~= 9 && (covSize(1) ~= 3 || covSize(2) ~= 3) && ...
33 | (covSize(1) ~= 6 || covSize(2) ~= 6)
34 | error('CheckAndResizeCov:Invalid2DCov','2D Covariance matrix must have 9 columns or be a 3x3 or 6x6 matrix!');
35 | end
36 | % Resize down to a 3x3 if a 6x6 was passed in
37 | if covSize(1) == 6 && covSize(2) == 6
38 | cov = cov(1:3,1:3);
39 | covSize = size(cov);
40 | end
41 | if covSize(1) == 1
42 | cov = repmat(cov,numR,1);
43 | elseif covSize(1) == 3 && covSize(2) == 3
44 | cov = reshape(permute(cov,[2 1]),1,9);
45 | cov = repmat(cov,numR,1);
46 | elseif covSize(1) ~= numR
47 | error('CheckAndResizeCov:RowCountMismatch2D','2D Covariance cannot be resized to match r matrix');
48 | end
49 | % If a 3D covariance matrix was passed in
50 | elseif size(covSize,2) == 3
51 | % The 3D matrix should have the dimension 3x3xnumR or 6x6xnumR
52 | if (covSize(1) ~= 3 || covSize(2) ~= 3 || covSize(3) ~= numR) && ...
53 | (covSize(1) ~= 6 || covSize(2) ~= 6 || covSize(3) ~= numR)
54 | error('CheckAndResizeCov:Invalid3DCov','3D covariance matrix must be of size 3x3xnumR or 6x6xnumR');
55 | end
56 | % Resize down to a 3x3xnumR if a 6x6xnumR was passed in
57 | if covSize(1) == 6 && covSize(2) == 6
58 | cov = cov(1:3,1:3,:);
59 | end
60 | cov = reshape(permute(cov,[3 2 1]),numR,9);
61 | else
62 | error('CheckAndResizeCov:InvalidCov','Improperly sized covariance was detected');
63 | end
64 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/jacobian_E0_to_Xt.m:
--------------------------------------------------------------------------------
1 | function [JT,XT] = jacobian_E0_to_Xt(T,E0,fr,mu,errflag)
2 | % =========================================================================
3 | %
4 | % Use epoch equinoctial orbital element state, Ecent0, to calculate the
5 | % cartesian states, XcentT = (rvec',vvec')', for a set of offset times from
6 | % epoch, T = t-t0. Here Ecent0 represents the "center-of-expansion"
7 | % equinoctial elements used for the first-order Taylor series expansion of
8 | % the motion:
9 | % X(t) = Xcent(t) + Jcent(t) * (E0 - Ecent0)
10 | % or
11 | % E0 = Ecent0 + [Jcent(t)]^-1 * (X(t)-Xcent(t))
12 | % with
13 | % J(t) = [dX(t)/dE0] at E0 = Ecent0 (i.e., X(t) = Xcent(t))
14 | %
15 | % =========================================================================
16 | %
17 | % INPUT:
18 | %
19 | % T = Time offsets from initial (s). [NTx1] or [1xNT]
20 | % E0 = Equinoctial elements at initial time t0. [6x1]
21 | % [n,af,ag,chi,psi,lam]' at t = t0.
22 | % See Vallado and Alfano (2015) for details.
23 | % fr = Equinoctial element retrograde factor (optional, default = +1)
24 | % mu = Gravitational constant (optional).
25 | % errflag = Error flag (optional, default = 2)
26 | % 0 => No error or warning issued for F nonconvergence (not recommended)
27 | % 1 => Warning issued for F nonconvergence (not recommended)
28 | % 2 => Error issued for F nonconvergence (recommended)
29 | %
30 | % =========================================================================
31 | %
32 | % OUTPUT:
33 | %
34 | % JT = EpochEquinoctial-to-EphemerisCartesian transformations [6x6xNT]
35 | % XT = Center-of-expansion cartesian state ephemeris (km & km/s) [6xNT]
36 | %
37 | % =========================================================================
38 | %
39 | % REFERENCE:
40 | %
41 | % Vallado and Alfano (2015), AAS 15-537
42 | % Broucke and Cefola (1972), Celestial Mechanics, Vol.5, pp.303-310
43 | %
44 | % =========================================================================
45 |
46 | % Defaults and intializations
47 |
48 | Nargin = nargin; % Nargout = nargout;
49 |
50 | na = 3;
51 | if Nargin < na || isempty(fr)
52 | % Default to prograde equinoctial elements
53 | fr = 1;
54 | end
55 |
56 | na = na+1;
57 | if Nargin < na || isempty(mu)
58 | % Earth gravitational constant (EGM-96) [km^3/s^2]
59 | mu = 3.986004418e5;
60 | end
61 |
62 | na = na+1;
63 | if Nargin < na || isempty(errflag)
64 | errflag = 2;
65 | end
66 |
67 | % Number of ephemeris times
68 | NT = numel(T);
69 |
70 | % Calculate X(t)
71 | [rT,vT] = convert_equinoctial_to_cartesian( ...
72 | E0(1),E0(2),E0(3),E0(4),E0(5),E0(6),T, ...
73 | fr,mu,[],[],errflag);
74 | XT = [rT; vT];
75 |
76 | % Initialize dE(t)/dE(t0) STM
77 | phi = eye(6,6);
78 |
79 | % Initialize E(t)
80 | ET = E0;
81 |
82 | % Initialize output Jacobian array
83 | JT = NaN(6,6,NT);
84 |
85 | % Loop over times and calculate output
86 |
87 | for nT=1:NT
88 | % Calculate the equinoctial mean longitude at this time
89 | ET(6) = E0(6)+T(nT)*E0(1);
90 | % Define off-diagonal dE(t)/dE(t0) STM element
91 | phi(6,1) = T(nT);
92 | % Calculate dX(t)/dE(t) STM
93 | J = jacobian_equinoctial_to_cartesian(ET,XT(:,nT),fr,mu);
94 | % Calculate the dX(t)/dE0 STM
95 | JT(:,:,nT) = J*phi;
96 | end
97 |
98 | return
99 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/sample_data/oct5_data/cdm0.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "CCSDS_CDM_VERS": "1.0",
4 | "CREATION_DATE": "2024-10-04T03:34:25.108",
5 | "ORIGINATOR": "CSpoc",
6 | "MESSAGE_ID": "7407_conj0_1338",
7 | "TCA": "2024-10-05T17:10:36.475",
8 | "MISS_DISTANCE": "14923",
9 | "SAT1_OBJECT": "OBJECT1",
10 | "SAT1_OBJECT_DESIGNATOR": "12345",
11 | "SAT1_CATALOG_NAME": "SATCAT",
12 | "SAT1_INTERNATIONAL_DESIGNATOR": "2019-030E",
13 | "SAT1_EPHEMERIS_NAME": "NONE",
14 | "SAT1_COVARIANCE_METHOD": "CALCULATED",
15 | "SAT1_MANEUVERABLE": "NO",
16 | "SAT1_REFERENCE_FRAME": "ITRF",
17 | "SAT1_X": "2569.097065",
18 | "SAT1_Y": "2245.654904",
19 | "SAT1_Z": "6281.497978",
20 | "SAT1_X_DOT": "4.418769571",
21 | "SAT1_Y_DOT": "4.833547743",
22 | "SAT1_Z_DOT": "-3.526774282",
23 | "SAT1_CR_R": "16.466809999999988",
24 | "SAT1_CT_R": "-124109.12391596804",
25 | "SAT1_CT_T": "18938.469999999998",
26 | "SAT1_CN_R": "15.272398047558655",
27 | "SAT1_CN_T": "2452.061015626061",
28 | "SAT1_CN_N": "3.1734509999999996",
29 | "SAT1_CRDOT_R": "0.02171203999999999",
30 | "SAT1_CRDOT_T": "-411.05878366331143",
31 | "SAT1_CRDOT_N": "-0.002652613771943246",
32 | "SAT1_CRDOT_RDOT": "0.02171203999999999",
33 | "SAT1_CTDOT_R": "-0.0002704973468097328",
34 | "SAT1_CTDOT_T": "0.06805182225726791",
35 | "SAT1_CTDOT_N": "-1.681653824600242e-05",
36 | "SAT1_CTDOT_RDOT": "-7.643144107555492e-08",
37 | "SAT1_CTDOT_TDOT": "1.6743309999999926e-05",
38 | "SAT1_CNDOT_R": "2.5077447026675033e-05",
39 | "SAT1_CNDOT_T": "-0.0002619771444092925",
40 | "SAT1_CNDOT_N": "1.5047272707261375e-05",
41 | "SAT1_CNDOT_RDOT": "4.0999922806762933e-10",
42 | "SAT1_CNDOT_TDOT": "-2.6774131967338362e-11",
43 | "SAT1_CNDOT_NDOT": "8.919619999999988e-06",
44 | "SAT2_OBJECT": "OBJECT1",
45 | "SAT2_OBJECT_DESIGNATOR": "67891",
46 | "SAT2_CATALOG_NAME": "SATCAT",
47 | "SAT2_INTERNATIONAL_DESIGNATOR": "2021-030E",
48 | "SAT2_EPHEMERIS_NAME": "NONE",
49 | "SAT2_COVARIANCE_METHOD": "CALCULATED",
50 | "SAT2_MANEUVERABLE": "YES",
51 | "SAT2_REFERENCE_FRAME": "ITRF",
52 | "SAT2_X": "4684.39413",
53 | "SAT2_Y": "-1485.2901920000004",
54 | "SAT2_Z": "26229.795955999998",
55 | "SAT2_X_DOT": "16.037539142",
56 | "SAT2_Y_DOT": "12646.667095485998",
57 | "SAT2_Z_DOT": "5518.846451436",
58 | "SAT2_CR_R": "71140.79",
59 | "SAT2_CT_R": "-173447055517246.4",
60 | "SAT2_CT_T": "2955756000.0",
61 | "SAT2_CN_R": "73262356.0198991",
62 | "SAT2_CN_T": "-16541557533.518105",
63 | "SAT2_CN_N": "2172.732",
64 | "SAT2_CRDOT_R": "3395.637",
65 | "SAT2_CRDOT_T": "-10036658308451.043",
66 | "SAT2_CRDOT_N": "26304.7422813006",
67 | "SAT2_CRDOT_RDOT": "3395.637",
68 | "SAT2_CTDOT_R": "-1420.343747333301",
69 | "SAT2_CTDOT_T": "17713388.417774897",
70 | "SAT2_CTDOT_N": "-48.19052769752484",
71 | "SAT2_CTDOT_RDOT": "-20.40639119554754",
72 | "SAT2_CTDOT_TDOT": "0.02723944",
73 | "SAT2_CNDOT_R": "2654.7336674472876",
74 | "SAT2_CNDOT_T": "86797934.59951398",
75 | "SAT2_CNDOT_N": "234.35235697465833",
76 | "SAT2_CNDOT_RDOT": "-99.86165235094477",
77 | "SAT2_CNDOT_TDOT": "-0.0027195986896554718",
78 | "SAT2_CNDOT_NDOT": "0.14935299999999999"
79 | }
80 | ]
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/sample_data/oct5_data/cdm3.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "CCSDS_CDM_VERS": "1.0",
4 | "CREATION_DATE": "2024-10-05T03:15:53.042",
5 | "ORIGINATOR": "CSpoc",
6 | "MESSAGE_ID": "1671_conj3_9268",
7 | "TCA": "2024-10-05T17:10:36.475",
8 | "MISS_DISTANCE": "14579",
9 | "SAT1_OBJECT": "OBJECT1",
10 | "SAT1_OBJECT_DESIGNATOR": "12345",
11 | "SAT1_CATALOG_NAME": "SATCAT",
12 | "SAT1_INTERNATIONAL_DESIGNATOR": "2019-030E",
13 | "SAT1_EPHEMERIS_NAME": "NONE",
14 | "SAT1_COVARIANCE_METHOD": "CALCULATED",
15 | "SAT1_MANEUVERABLE": "NO",
16 | "SAT1_REFERENCE_FRAME": "ITRF",
17 | "SAT1_X": "2569.097065",
18 | "SAT1_Y": "2245.654904",
19 | "SAT1_Z": "6281.497978",
20 | "SAT1_X_DOT": "4.418769571",
21 | "SAT1_Y_DOT": "4.833547743",
22 | "SAT1_Z_DOT": "-3.526774282",
23 | "SAT1_CR_R": "9.460428",
24 | "SAT1_CT_R": "100.95118385368234",
25 | "SAT1_CT_T": "494.3005",
26 | "SAT1_CN_R": "13.374609096870207",
27 | "SAT1_CN_T": "259.87230439211817",
28 | "SAT1_CN_N": "3.337079",
29 | "SAT1_CRDOT_R": "0.0004868924999999991",
30 | "SAT1_CRDOT_T": "-0.23848413183125663",
31 | "SAT1_CRDOT_N": "-0.00024371647114500403",
32 | "SAT1_CRDOT_RDOT": "0.0004868924999999991",
33 | "SAT1_CTDOT_R": "-0.00010277619533014733",
34 | "SAT1_CTDOT_T": "-0.00031933176551537717",
35 | "SAT1_CTDOT_N": "-1.550394012729719e-05",
36 | "SAT1_CTDOT_RDOT": "3.824140680442102e-10",
37 | "SAT1_CTDOT_TDOT": "1.0876239999999834e-05",
38 | "SAT1_CNDOT_R": "1.837409542882393e-05",
39 | "SAT1_CNDOT_T": "0.00016486648335839135",
40 | "SAT1_CNDOT_N": "1.5561972107337534e-05",
41 | "SAT1_CNDOT_RDOT": "-2.0723211585924206e-10",
42 | "SAT1_CNDOT_TDOT": "-2.1257833532321998e-11",
43 | "SAT1_CNDOT_NDOT": "8.516542999999602e-06",
44 | "SAT2_OBJECT": "OBJECT1",
45 | "SAT2_OBJECT_DESIGNATOR": "67891",
46 | "SAT2_CATALOG_NAME": "SATCAT",
47 | "SAT2_INTERNATIONAL_DESIGNATOR": "2021-030E",
48 | "SAT2_EPHEMERIS_NAME": "NONE",
49 | "SAT2_COVARIANCE_METHOD": "CALCULATED",
50 | "SAT2_MANEUVERABLE": "YES",
51 | "SAT2_REFERENCE_FRAME": "ITRF",
52 | "SAT2_X": "4665.49413",
53 | "SAT2_Y": "-1347.5901919999997",
54 | "SAT2_Z": "25913.695956",
55 | "SAT2_X_DOT": "15.837539142",
56 | "SAT2_Y_DOT": "12646.667095485998",
57 | "SAT2_Z_DOT": "5518.846451436",
58 | "SAT2_CR_R": "68518.15000000001",
59 | "SAT2_CT_R": "-163998711741309.6",
60 | "SAT2_CT_T": "2927633000.0",
61 | "SAT2_CN_R": "71959560.65875487",
62 | "SAT2_CN_T": "-23899082735.30432",
63 | "SAT2_CN_N": "2171.335000000001",
64 | "SAT2_CRDOT_R": "3363.2929999999983",
65 | "SAT2_CRDOT_T": "-9846467682226.9",
66 | "SAT2_CRDOT_N": "34722.3962751339",
67 | "SAT2_CRDOT_RDOT": "3363.2929999999983",
68 | "SAT2_CTDOT_R": "-1346.8315032614892",
69 | "SAT2_CTDOT_T": "15849903.560941886",
70 | "SAT2_CTDOT_N": "-47.94457892416641",
71 | "SAT2_CTDOT_RDOT": "-18.264878606315833",
72 | "SAT2_CTDOT_TDOT": "0.026997380000000005",
73 | "SAT2_CNDOT_R": "2657.4141866193004",
74 | "SAT2_CNDOT_T": "85617046.08932368",
75 | "SAT2_CNDOT_N": "234.1003829657977",
76 | "SAT2_CNDOT_RDOT": "-98.47141484241098",
77 | "SAT2_CNDOT_TDOT": "-0.0027234595338783167",
78 | "SAT2_CNDOT_NDOT": "0.14930469999999996"
79 | }
80 | ]
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/sample_data/oct5_data/cdm2.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "CCSDS_CDM_VERS": "1.0",
4 | "CREATION_DATE": "2024-10-04T18:19:27.022",
5 | "ORIGINATOR": "CSpoc",
6 | "MESSAGE_ID": "2352_conj2_5250",
7 | "TCA": "2024-10-05T17:10:36.475",
8 | "MISS_DISTANCE": "14475",
9 | "SAT1_OBJECT": "OBJECT1",
10 | "SAT1_OBJECT_DESIGNATOR": "12345",
11 | "SAT1_CATALOG_NAME": "SATCAT",
12 | "SAT1_INTERNATIONAL_DESIGNATOR": "2019-030E",
13 | "SAT1_EPHEMERIS_NAME": "NONE",
14 | "SAT1_COVARIANCE_METHOD": "CALCULATED",
15 | "SAT1_MANEUVERABLE": "NO",
16 | "SAT1_REFERENCE_FRAME": "ITRF",
17 | "SAT1_X": "2569.097065",
18 | "SAT1_Y": "2245.654904",
19 | "SAT1_Z": "6281.497978",
20 | "SAT1_X_DOT": "4.418769571",
21 | "SAT1_Y_DOT": "4.833547743",
22 | "SAT1_Z_DOT": "-3.526774282",
23 | "SAT1_CR_R": "11.30329",
24 | "SAT1_CT_R": "-1736.0459177032214",
25 | "SAT1_CT_T": "1406.0959999999998",
26 | "SAT1_CN_R": "11.450941880675588",
27 | "SAT1_CN_T": "204.01998943137414",
28 | "SAT1_CN_N": "3.319463999999999",
29 | "SAT1_CRDOT_R": "0.0015412149999999955",
30 | "SAT1_CRDOT_T": "-2.1589377491633517",
31 | "SAT1_CRDOT_N": "-0.0001754131058154928",
32 | "SAT1_CRDOT_RDOT": "0.0015412149999999955",
33 | "SAT1_CTDOT_R": "-0.00014407361589835086",
34 | "SAT1_CTDOT_T": "0.0006101929798184829",
35 | "SAT1_CTDOT_N": "-1.3096352890776559e-05",
36 | "SAT1_CTDOT_RDOT": "-5.944793822298559e-10",
37 | "SAT1_CTDOT_TDOT": "1.2788699999999509e-05",
38 | "SAT1_CNDOT_R": "1.2313530455547843e-05",
39 | "SAT1_CNDOT_T": "0.00024296227126581737",
40 | "SAT1_CNDOT_N": "1.6925774251709703e-05",
41 | "SAT1_CNDOT_RDOT": "-3.218738348956514e-10",
42 | "SAT1_CNDOT_TDOT": "-1.4154215132856788e-11",
43 | "SAT1_CNDOT_NDOT": "8.802304999999936e-06",
44 | "SAT2_OBJECT": "OBJECT1",
45 | "SAT2_OBJECT_DESIGNATOR": "67891",
46 | "SAT2_CATALOG_NAME": "SATCAT",
47 | "SAT2_INTERNATIONAL_DESIGNATOR": "2021-030E",
48 | "SAT2_EPHEMERIS_NAME": "NONE",
49 | "SAT2_COVARIANCE_METHOD": "CALCULATED",
50 | "SAT2_MANEUVERABLE": "YES",
51 | "SAT2_REFERENCE_FRAME": "ITRF",
52 | "SAT2_X": "4663.5941299999995",
53 | "SAT2_Y": "-1304.8901919999998",
54 | "SAT2_Z": "25819.095956",
55 | "SAT2_X_DOT": "15.837539142",
56 | "SAT2_Y_DOT": "12646.667095485998",
57 | "SAT2_Z_DOT": "5518.846451436",
58 | "SAT2_CR_R": "68469.38",
59 | "SAT2_CT_R": "-163366625861765.34",
60 | "SAT2_CT_T": "2918959000.0000005",
61 | "SAT2_CN_R": "71915714.42231454",
62 | "SAT2_CN_T": "-23710013898.2132",
63 | "SAT2_CN_N": "2170.8850000000007",
64 | "SAT2_CRDOT_R": "3353.289999999999",
65 | "SAT2_CRDOT_T": "-9788098835180.406",
66 | "SAT2_CRDOT_N": "34493.37382121202",
67 | "SAT2_CRDOT_RDOT": "3353.289999999999",
68 | "SAT2_CTDOT_R": "-1348.4149760644132",
69 | "SAT2_CTDOT_T": "15903045.190279152",
70 | "SAT2_CTDOT_N": "-47.94694154153545",
71 | "SAT2_CTDOT_RDOT": "-18.32563855207348",
72 | "SAT2_CTDOT_TDOT": "0.027011589999999967",
73 | "SAT2_CNDOT_R": "2659.732660844721",
74 | "SAT2_CNDOT_T": "85254697.53509147",
75 | "SAT2_CNDOT_N": "234.04542544087747",
76 | "SAT2_CNDOT_RDOT": "-98.04438819670375",
77 | "SAT2_CNDOT_TDOT": "-0.0027233793844917732",
78 | "SAT2_CNDOT_NDOT": "0.1492901"
79 | }
80 | ]
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/sample_data/oct5_data/cdm1.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "CCSDS_CDM_VERS": "1.0",
4 | "CREATION_DATE": "2024-10-04T12:11:49.036",
5 | "ORIGINATOR": "CSpoc",
6 | "MESSAGE_ID": "5372_conj1_5222",
7 | "TCA": "2024-10-05T17:10:36.475",
8 | "MISS_DISTANCE": "14544",
9 | "SAT1_OBJECT": "OBJECT1",
10 | "SAT1_OBJECT_DESIGNATOR": "12345",
11 | "SAT1_CATALOG_NAME": "SATCAT",
12 | "SAT1_INTERNATIONAL_DESIGNATOR": "2019-030E",
13 | "SAT1_EPHEMERIS_NAME": "NONE",
14 | "SAT1_COVARIANCE_METHOD": "CALCULATED",
15 | "SAT1_MANEUVERABLE": "NO",
16 | "SAT1_REFERENCE_FRAME": "ITRF",
17 | "SAT1_X": "2569.097065",
18 | "SAT1_Y": "2245.654904",
19 | "SAT1_Z": "6281.497978",
20 | "SAT1_X_DOT": "4.418769571",
21 | "SAT1_Y_DOT": "4.833547743",
22 | "SAT1_Z_DOT": "-3.526774282",
23 | "SAT1_CR_R": "12.438180000000003",
24 | "SAT1_CT_R": "-2859.9593342082176",
25 | "SAT1_CT_T": "3143.8580000000006",
26 | "SAT1_CN_R": "11.9965192167563",
27 | "SAT1_CN_T": "617.3295806639882",
28 | "SAT1_CN_N": "3.2434519999999987",
29 | "SAT1_CRDOT_R": "0.003560762999999993",
30 | "SAT1_CRDOT_T": "-11.17429351063103",
31 | "SAT1_CRDOT_N": "-0.0006064604505376215",
32 | "SAT1_CRDOT_RDOT": "0.003560762999999993",
33 | "SAT1_CTDOT_R": "-0.00017415206257147947",
34 | "SAT1_CTDOT_T": "-0.0013124245331266138",
35 | "SAT1_CTDOT_N": "-1.3809345175536252e-05",
36 | "SAT1_CTDOT_RDOT": "1.7064174107178696e-09",
37 | "SAT1_CTDOT_TDOT": "1.4082519999999975e-05",
38 | "SAT1_CNDOT_R": "1.9231443904931584e-05",
39 | "SAT1_CNDOT_T": "4.198231150531984e-05",
40 | "SAT1_CNDOT_N": "1.5660229650232342e-05",
41 | "SAT1_CNDOT_RDOT": "-1.582394681859056e-10",
42 | "SAT1_CNDOT_TDOT": "-2.1936230727864984e-11",
43 | "SAT1_CNDOT_NDOT": "8.604338999999885e-06",
44 | "SAT2_OBJECT": "OBJECT1",
45 | "SAT2_OBJECT_DESIGNATOR": "67891",
46 | "SAT2_CATALOG_NAME": "SATCAT",
47 | "SAT2_INTERNATIONAL_DESIGNATOR": "2021-030E",
48 | "SAT2_EPHEMERIS_NAME": "NONE",
49 | "SAT2_COVARIANCE_METHOD": "CALCULATED",
50 | "SAT2_MANEUVERABLE": "YES",
51 | "SAT2_REFERENCE_FRAME": "ITRF",
52 | "SAT2_X": "4663.89413",
53 | "SAT2_Y": "-1329.8901919999998",
54 | "SAT2_Z": "25882.795955999998",
55 | "SAT2_X_DOT": "15.837539142",
56 | "SAT2_Y_DOT": "12646.667095485998",
57 | "SAT2_Z_DOT": "5518.846451436",
58 | "SAT2_CR_R": "68744.55000000002",
59 | "SAT2_CT_R": "-164516051071393.47",
60 | "SAT2_CT_T": "2924869999.999999",
61 | "SAT2_CN_R": "72054166.0937517",
62 | "SAT2_CN_T": "-22723563949.867527",
63 | "SAT2_CN_N": "2171.175",
64 | "SAT2_CRDOT_R": "3360.1049999999996",
65 | "SAT2_CRDOT_T": "-9827853287582.252",
66 | "SAT2_CRDOT_N": "33367.9076961158",
67 | "SAT2_CRDOT_RDOT": "3360.1049999999996",
68 | "SAT2_CTDOT_R": "-1353.7353720994918",
69 | "SAT2_CTDOT_T": "16012071.648502622",
70 | "SAT2_CTDOT_N": "-47.961060064859275",
71 | "SAT2_CTDOT_RDOT": "-18.451090163742993",
72 | "SAT2_CTDOT_TDOT": "0.027021619999999986",
73 | "SAT2_CNDOT_R": "2658.8393484284798",
74 | "SAT2_CNDOT_T": "85496953.755255",
75 | "SAT2_CNDOT_N": "234.09259233918314",
76 | "SAT2_CNDOT_RDOT": "-98.32962134287756",
77 | "SAT2_CNDOT_TDOT": "-0.0027230776774019596",
78 | "SAT2_CNDOT_NDOT": "0.1492"
79 | }
80 | ]
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/sample_data/oct5_data/cdm4.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "CCSDS_CDM_VERS": "1.0",
4 | "CREATION_DATE": "2024-10-05T10:59:22.033",
5 | "ORIGINATOR": "CSpoc",
6 | "MESSAGE_ID": "6530_conj4_1024",
7 | "TCA": "2024-10-05T17:10:36.475",
8 | "MISS_DISTANCE": "14510",
9 | "SAT1_OBJECT": "OBJECT1",
10 | "SAT1_OBJECT_DESIGNATOR": "12345",
11 | "SAT1_CATALOG_NAME": "SATCAT",
12 | "SAT1_INTERNATIONAL_DESIGNATOR": "2019-030E",
13 | "SAT1_EPHEMERIS_NAME": "NONE",
14 | "SAT1_COVARIANCE_METHOD": "CALCULATED",
15 | "SAT1_MANEUVERABLE": "NO",
16 | "SAT1_REFERENCE_FRAME": "ITRF",
17 | "SAT1_X": "2569.097065",
18 | "SAT1_Y": "2245.654904",
19 | "SAT1_Z": "6281.497978",
20 | "SAT1_X_DOT": "4.418769571",
21 | "SAT1_Y_DOT": "4.833547743",
22 | "SAT1_Z_DOT": "-3.526774282",
23 | "SAT1_CR_R": "11.506860000000005",
24 | "SAT1_CT_R": "1248.036661134601",
25 | "SAT1_CT_T": "259.5581000000001",
26 | "SAT1_CN_R": "17.916002509223198",
27 | "SAT1_CN_T": "245.7711485383819",
28 | "SAT1_CN_N": "3.8349209999999996",
29 | "SAT1_CRDOT_R": "0.00022726939999999785",
30 | "SAT1_CRDOT_T": "-0.05799527808110657",
31 | "SAT1_CRDOT_N": "-0.00021179419934301688",
32 | "SAT1_CRDOT_RDOT": "0.00022726939999999785",
33 | "SAT1_CTDOT_R": "-0.00015491341398556023",
34 | "SAT1_CTDOT_T": "-0.0015060774553316179",
35 | "SAT1_CTDOT_N": "-2.0952096822822975e-05",
36 | "SAT1_CTDOT_RDOT": "1.4689758301709094e-09",
37 | "SAT1_CTDOT_TDOT": "1.3466079999999983e-05",
38 | "SAT1_CNDOT_R": "2.6661413068295898e-05",
39 | "SAT1_CNDOT_T": "0.00031937485126386103",
40 | "SAT1_CNDOT_N": "2.1609511370945364e-05",
41 | "SAT1_CNDOT_RDOT": "-3.382421549510018e-10",
42 | "SAT1_CNDOT_TDOT": "-3.128348087699916e-11",
43 | "SAT1_CNDOT_NDOT": "1.0368329999999764e-05",
44 | "SAT2_OBJECT": "OBJECT1",
45 | "SAT2_OBJECT_DESIGNATOR": "67891",
46 | "SAT2_CATALOG_NAME": "SATCAT",
47 | "SAT2_INTERNATIONAL_DESIGNATOR": "2021-030E",
48 | "SAT2_EPHEMERIS_NAME": "NONE",
49 | "SAT2_COVARIANCE_METHOD": "CALCULATED",
50 | "SAT2_MANEUVERABLE": "YES",
51 | "SAT2_REFERENCE_FRAME": "ITRF",
52 | "SAT2_X": "4659.49413",
53 | "SAT2_Y": "-1319.7901920000004",
54 | "SAT2_Z": "25850.995956",
55 | "SAT2_X_DOT": "15.837539142",
56 | "SAT2_Y_DOT": "12646.667095485998",
57 | "SAT2_Z_DOT": "5518.846451436",
58 | "SAT2_CR_R": "67978.79000000001",
59 | "SAT2_CT_R": "-162130280857593.2",
60 | "SAT2_CT_T": "2922881000.0",
61 | "SAT2_CN_R": "71696592.84559105",
62 | "SAT2_CN_T": "-25896048808.354958",
63 | "SAT2_CN_N": "2171.0660000000007",
64 | "SAT2_CRDOT_R": "3357.8220000000006",
65 | "SAT2_CRDOT_T": "-9814496912200.408",
66 | "SAT2_CRDOT_N": "37010.43898900366",
67 | "SAT2_CRDOT_RDOT": "3357.8220000000006",
68 | "SAT2_CTDOT_R": "-1332.471339849396",
69 | "SAT2_CTDOT_T": "15492739.465907317",
70 | "SAT2_CTDOT_N": "-47.89920848575927",
71 | "SAT2_CTDOT_RDOT": "-17.854404929205973",
72 | "SAT2_CTDOT_TDOT": "0.026952349999999986",
73 | "SAT2_CNDOT_R": "2657.53461602947",
74 | "SAT2_CNDOT_T": "85412367.9110327",
75 | "SAT2_CNDOT_N": "234.04549101615999",
76 | "SAT2_CNDOT_RDOT": "-98.23003979101016",
77 | "SAT2_CNDOT_TDOT": "-0.0027243267415416354",
78 | "SAT2_CNDOT_NDOT": "0.14929579999999992"
79 | }
80 | ]
--------------------------------------------------------------------------------
/on-orbit-frontend/src/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useState } from 'react';
4 | import { useRouter } from 'next/navigation';
5 | import Footer from '@/components/footer/page';
6 | import Csa from '@/components/csa/csa';
7 |
8 | export default function Login() {
9 | const [email, setEmail] = useState('');
10 | const [password, setPassword] = useState('');
11 | const [error, setError] = useState(null);
12 | const router = useRouter();
13 |
14 | const handleSubmit = async (e: React.FormEvent) => {
15 | e.preventDefault();
16 | setError(null);
17 |
18 | try {
19 | const response = await fetch('http://localhost:8000/api/login/', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | body: JSON.stringify({ email, password }),
25 | });
26 |
27 | if (!response.ok) {
28 | throw new Error(`Login failed. Please check your credentials.`);
29 | }
30 |
31 | const data = await response.json();
32 |
33 | localStorage.setItem('token', data.access_token);
34 |
35 | router.push('/dashboard');
36 | } catch (err) {
37 | if (err instanceof Error) {
38 | setError(err.message);
39 | } else {
40 | setError('An unknown error occurred.');
41 | }
42 | }
43 | };
44 |
45 | return (
46 |
47 |
48 |
49 |
Login
50 |
51 | {error && (
52 |
53 | {error}
54 |
55 | )}
56 |
57 |
93 |
94 | {/*
*/}
95 |
96 |
97 | );
98 | }
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/LinearConjDuration_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = { ...
2 | matlab.unittest.fixtures.PathFixture('..')}) ...
3 | LinearConjDuration_UnitTest < matlab.unittest.TestCase
4 | %% Series of Tests intended to test the Linear Conjunction Duration
5 | % Provides Unit Testing of the Following:
6 | % LinearConjDuration.m
7 | % conj_bounds_Coppola.m
8 |
9 | methods (Test)
10 | function test01(testCase)
11 | % (based on the conjunction plotted in Figure 1 of Hall et al. AAS 18-244)
12 | Accuracy = 5E-8;
13 | exptau0 = -3.420684860400457e-01;
14 | exptau1 = +3.904469210852202e-01;
15 |
16 | r1 = [-9.842093647442480e+05 +3.931926264086390e+05 +6.991224004693392e+06];
17 | v1 = [+4.883454112123840e+03 +5.689294308456769e+03 +3.665363038076542e+02];
18 | c1 = [+4.976052019427295e+04 +5.787056034675250e+04 +3.370244323972227e+03; ...
19 | +5.787056034675250e+04 +6.730871246008216e+04 +3.926688367496737e+03; ...
20 | +3.370244323972227e+03 +3.926688367496737e+03 +2.461405204706109e+02];
21 | r2 = [-9.839848654647591e+05 +3.936434850314705e+05 +6.991219473018020e+06];
22 | v2 = [+1.509248147563707e+03 +7.373003029689082e+03 -1.492499807334025e+02];
23 | c2 = [+4.245099621043838e+04 +2.065963368930267e+05 -5.010043216505899e+03; ...
24 | +2.065963368930267e+05 +1.005872352933331e+06 -2.434884753961109e+04; ...
25 | -5.010043216505899e+03 -2.434884753961109e+04 +6.131211497491000e+02];
26 | HBR = 20;
27 | [acttau0,acttau1,dtau,taum,delt] = LinearConjDuration(r1,v1,c1,r2,v2,c2,HBR);
28 |
29 | % Verify Expected Solution
30 | testCase.verifyEqual(acttau0,exptau0,'RelTol',Accuracy);
31 | testCase.verifyEqual(acttau1,exptau1,'RelTol',Accuracy);
32 | % Verify Output values derived from these are accurate
33 | testCase.verifyEqual(dtau,acttau1-acttau0,'RelTol',Accuracy);
34 | testCase.verifyEqual(taum,(acttau1+acttau0)/2,'RelTol',Accuracy);
35 | testCase.verifyEqual(delt,max([dtau abs(acttau0) abs(acttau1)]),'RelTol',Accuracy);
36 | end
37 |
38 | function test02(testCase)
39 | % (based on the conjunction plotted in Figure 4 of Hall et al. AAS 18-244)
40 | Accuracy = 5E-8;
41 | exptau0 = +3.589250204918186e+00;
42 | exptau1 = +5.174956423245569e+00;
43 |
44 | r1 = [+7.024372797415487e+06 -6.791385617713347e+05 -5.967897695834826e+05];
45 | v1 = [-2.860274625876989e+02 +9.622903147818041e+03 -1.360862306955150e+03];
46 | c1 = [+9.607519669421256e+02 -8.200162426475858e+03 +1.445470803475952e+03; ...
47 | -8.200162426475858e+03 +9.123404938408395e+05 -1.329871062174348e+05; ...
48 | +1.445470803475952e+03 -1.329871062174348e+05 +1.978319035209270e+04];
49 | r2 = [+7.029150207165684e+06 -6.187859247558538e+05 -5.438025870728889e+05];
50 | v2 = [+7.142872072322662e+02 +2.012989242434993e+03 +7.216509095006236e+03];
51 | c2 = [+1.399046667137783e+08 +3.966346832929837e+08 +1.424266116056896e+09; ...
52 | +3.966346832929837e+08 +1.124492680655296e+09 +4.037825954063638e+09; ...
53 | +1.424266116056896e+09 +4.037825954063638e+09 +1.449981900252032e+10];
54 | HBR = 52.84;
55 | [acttau0,acttau1,dtau,taum,delt] = LinearConjDuration(r1,v1,c1,r2,v2,c2,HBR);
56 |
57 | % Verify Expected Solution
58 | testCase.verifyEqual(acttau0,exptau0,'RelTol',Accuracy);
59 | testCase.verifyEqual(acttau1,exptau1,'RelTol',Accuracy);
60 | % Verify Output values derived from these are accurate
61 | testCase.verifyEqual(dtau,acttau1-acttau0,'RelTol',Accuracy);
62 | testCase.verifyEqual(taum,(acttau1+acttau0)/2,'RelTol',Accuracy);
63 | testCase.verifyEqual(delt,max([dtau abs(acttau0) abs(acttau1)]),'RelTol',Accuracy);
64 | end
65 |
66 | end
67 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/FindNearbyCA.m:
--------------------------------------------------------------------------------
1 | function [dTCA,X1CA,X2CA] = FindNearbyCA(X1,X2,MotionMode,RelTol)
2 | % FindNearbyCA - Find the close-approach (CA) point from an input primary
3 | % and secondary position/velocity inertial state.
4 | %
5 | % Syntax: [TCA,X1CA,X2CA] = FindCA(X1,X2,MotionMode,RelTol)
6 | %
7 | % X1 - Primary object's pos/vel state vector in ECI coordinates
8 | % (6x1) [m & m/s] or [km & km/s]
9 | % X2 - Secondary object's pos/vel state vector in ECI coordinates
10 | % (6x1) [m & m/s] or [km & km/s]
11 | % MotionMode - 'LINEAR' (currently implemented)
12 | % - 'TWOBODY' (possible future addition)
13 | % RelTol - Tolerance for finding CA (reserved for TWOBODY MotionMode)
14 | %
15 | % Outputs:
16 | % dTCA - Offset time to CA (s)
17 | % X1CA - Primary ECI state at CA (6x1) [m & m/s] or [km & km/s]
18 | % X2CA - Seconary ECI state at CA (6x1) [m & m/s] or [km & km/s]
19 | %
20 | % Example/Validation Cases:
21 | % Conjunctions with unacceptably large TCA offsets:
22 | % 25544_conj_44437_20190727_160507_20190727_160507 (dTCA = 497.068 s)
23 | % 43042_conj_43043_20190505_061624_20190428_061624 (dTCA = 28.329 s)
24 | % 25544_conj_44437_20190803_160507_20190727_160507 (dTCA = 27.198 s)
25 | % 26998_conj_81790_20180707_061819_20180707_061819 (dTCA = 0.173 s)
26 | % Conjunctions with negligbly small TCA offsets:
27 | % 40115_conj_24925_20181003_073833_20180928_144418 (dTCA = 9.91E-06 s)
28 | % 43689_conj_31620_20190725_050922_20190719_152452 (dTCA = 1.92E-08 s)
29 | %
30 | % Other m-files required: None
31 | % Subfunctions: None
32 | % MAT-files required: None
33 | %
34 | % See also: None
35 | %
36 | % Last revision: 2018-SEP-04
37 | %
38 | % ----------------- BEGIN CODE -----------------
39 |
40 | % Set up defaults
41 |
42 | Nargin = nargin;
43 |
44 | if Nargin < 3 || isempty(MotionMode)
45 | MotionMode = 'LINEAR';
46 | else
47 | MotionMode = upper(MotionMode);
48 | end
49 |
50 | % Initialize motion mode
51 |
52 | switch MotionMode
53 | case 'LINEAR'
54 | % Linear motion mode
55 | motion = 1;
56 | case 'TWOBODY'
57 | % 2-body motion mode
58 | motion = 2;
59 | % Get relative tolerance for convergence
60 | if Nargin < 4 || isempty(RelTol)
61 | RelTol = 1E-12; %#ok
62 | end
63 | otherwise
64 | error('Invalid motion mode');
65 | end
66 |
67 | % Handle different motion modes
68 |
69 | if motion == 1 % Linear motion
70 |
71 | % Primary and secondary cartesian positions and velocities
72 | r1 = X1(1:3);
73 | v1 = X1(4:6);
74 | r2 = X2(1:3);
75 | v2 = X2(4:6);
76 |
77 | % Relative velocity
78 | v = v2-v1;
79 | vmag2 = v'*v;
80 |
81 | % Handle zero relative velocity case
82 | if vmag2 == 0
83 | % No TCA offset can be calculated for zero relative velocity
84 | dTCA = NaN;
85 | % Primary and secondary CA positions remain unchanged for the
86 | % case of linear motion with zero relative velocity
87 | r1CA = r1;
88 | r2CA = r2;
89 | else
90 | % TCA offset for linear relative motion
91 | dTCA = -((r2-r1)'*v)/vmag2;
92 | % Primary and secondary positions at the linear-motion TCA
93 | r1CA = r1+dTCA*v1;
94 | r2CA = r2+dTCA*v2;
95 | end
96 |
97 | % Return final linear motion states
98 |
99 | X1CA = [r1CA; v1];
100 | X2CA = [r2CA; v2];
101 |
102 | elseif motion == 2 % Two-body motion
103 |
104 | error('TWOBODY motion mode not implemented');
105 |
106 | end
107 |
108 | return;
109 |
110 | % ----------------- END OF CODE ------------------
111 | %
112 | % Please record any changes to the software in the change history
113 | % shown below:
114 | %
115 | % ----------------- CHANGE HISTORY ------------------
116 | % Developer | Date | Description
117 | % ---------------------------------------------------
118 | % D. Hall | 2019-AUG-07 | Initial Development
119 | % D. Hall | 2019-SEP-04 | Expanded comments and description
120 | %
121 |
122 | end
--------------------------------------------------------------------------------
/on-orbit-frontend/src/app/maneuvering/linear/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import Highcharts from "highcharts";
5 | import HighchartsReact from "highcharts-react-official";
6 | import { useParams } from "next/navigation";
7 | import Loading from "@/app/loading";
8 | import Link from "next/link";
9 |
10 | interface TrajectoryPoint {
11 | T_hours_before_TCA: number;
12 | delta_v_m_s: number;
13 | pc_value: number;
14 | miss_distance: number;
15 | sat1_position: number[];
16 | sat1_velocity: number[];
17 | }
18 |
19 | export default function ManeuveringDashboard() {
20 | const params = useParams();
21 | const id = params?.id as string | undefined;
22 | const [trajectory, setTrajectory] = useState([]);
23 | const [loading, setLoading] = useState(true);
24 | const [error, setError] = useState("");
25 |
26 | useEffect(() => {
27 | async function fetchTradespace() {
28 | try {
29 | const accessToken = localStorage.getItem("token");
30 | if (!accessToken) {
31 | throw new Error("Please login to view this page");
32 | }
33 |
34 | const headers = {
35 | "Content-Type": "application/json",
36 | Authorization: `Bearer ${accessToken}`,
37 | };
38 |
39 | // Use the id from the URL as the cdm_id.
40 | const response = await fetch("http://localhost:8000/api/tradespace/linear/", {
41 | method: "POST",
42 | headers,
43 | body: JSON.stringify({ cdm_id: Number(id) }),
44 | });
45 | if (!response.ok) {
46 | throw new Error("Failed to fetch tradespace data");
47 | }
48 | const data = await response.json();
49 | // Backend returns an object with a "trajectory" field.
50 | setTrajectory(data.trajectory);
51 | } catch (err: any) {
52 | console.error(err);
53 | setError(err.message || "An error occurred");
54 | } finally {
55 | setLoading(false);
56 | }
57 | }
58 | if (id) fetchTradespace();
59 | }, [id]);
60 |
61 | // Sort trajectory by time (from 24 hr down to 0 hr)
62 | const sortedTrajectory = [...trajectory].sort(
63 | (a, b) => a.T_hours_before_TCA - b.T_hours_before_TCA
64 | );
65 |
66 | // Extract data for Highcharts
67 | const timeData = sortedTrajectory.map((pt) => pt.T_hours_before_TCA);
68 | const missDistanceData = sortedTrajectory.map((pt) => pt.miss_distance);
69 | const pcData = sortedTrajectory.map((pt) => pt.pc_value);
70 |
71 | const missDistanceOptions = {
72 | title: { text: "Miss Distance over Time" },
73 | xAxis: {
74 | title: { text: "Time Before TCA (hours)" },
75 | categories: timeData.map((t) => t.toFixed(2)),
76 | },
77 | yAxis: { title: { text: "Miss Distance" } },
78 | series: [{ name: "Miss Distance", data: missDistanceData }],
79 | };
80 |
81 | const pcOptions = {
82 | title: { text: "Collision Probability over Time" },
83 | xAxis: {
84 | title: { text: "Time Before TCA (hours)" },
85 | categories: timeData.map((t) => t.toFixed(2)),
86 | },
87 | yAxis: { title: { text: "Collision Probability" } },
88 | series: [{ name: "Collision Probability", data: pcData }],
89 | };
90 |
91 | if (loading) return
;
92 | if (error) return Error: {error}
;
93 |
94 | return (
95 |
96 |
97 |
109 |
110 |
Tradespace Trajectory
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | );
119 | }
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/eig2x2_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = ...
2 | {matlab.unittest.fixtures.PathFixture('..')}) ...
3 | eig2x2_UnitTest < matlab.unittest.TestCase
4 | % eig2x2_UnitTest - Unit test for eig2x2
5 | %
6 | % =========================================================================
7 | %
8 | % Copyright (c) 2023 United States Government as represented by the
9 | % Administrator of the National Aeronautics and Space Administration.
10 | % All Rights Reserved.
11 | %
12 | % =========================================================================
13 | %
14 | % Initial version: Jul 2023; Latest update: Jul 2023
15 | %
16 | % ----------------- BEGIN CODE -----------------
17 |
18 | methods (Test)
19 | % Tests 1000 random 2x2 real diagonal matrices
20 | function testDiagonal (testCase)
21 | a = 2000 * rand(1000, 1) - 1000;
22 | b = zeros(1000, 1);
23 | d = 2000 * rand(1000, 1) - 1000;
24 |
25 | [V1, V2, L1, L2] = eig2x2([a, b, d]);
26 |
27 | % Swap values of a and d so a > d (for checking eigenvalues)
28 | t = a;
29 | i = a < d;
30 | a(i) = d(i);
31 | d(i) = t(i);
32 |
33 | testCase.verifyEqual(a, L1, 'RelTol', 1E-5, 'AbsTol', 1E-5);
34 | testCase.verifyEqual(d, L2, 'RelTol', 1E-5, 'AbsTol', 1E-5);
35 | testCase.verifyEqual(V1, repmat([1, 0], 1000, 1));
36 | testCase.verifyEqual(V2, repmat([0, 1], 1000, 1));
37 | end
38 |
39 | % Tests 2x2 zero matrix
40 | function testZeroMatrix (testCase)
41 | [V1, V2, L1, L2] = eig2x2([0, 0, 0]);
42 |
43 | testCase.verifyEqual([L1 L2], zeros(1, 2));
44 | testCase.verifyEqual([V1; V2], eye(2));
45 | end
46 |
47 | % Tests 1000 random 2x2 real symmetric matrices against MATLAB's
48 | % eig() function
49 |
50 | % Note that the eigenvectors sometimes differ by a factor of -1
51 | function testRandomMatrices (testCase)
52 | a = 2000 * rand(1000, 1) - 1000;
53 | b = 2000 * rand(1000, 1) - 1000;
54 | d = 2000 * rand(1000, 1) - 1000;
55 |
56 | [V1, V2, L1, L2] = eig2x2([a, b, d]);
57 |
58 | for i = 1:1000
59 | [V, D] = eig([a(i), b(i); b(i), d(i)], 'vector');
60 | [D, idx] = sort(D, 'descend');
61 | V = V(:, idx);
62 | testCase.verifyEqual([L1(i); L2(i)], D, 'RelTol', 1E-5, 'AbsTol', 1E-5);
63 | f = abs([V1(i, :)', V2(i, :)'] ./ V) - 1;
64 | f(f <= 1E-5) = 0;
65 | testCase.verifyEqual(f, zeros(2));
66 | end
67 | end
68 |
69 | % Tests 1000 random 2x2 real symmetric matrices with small
70 | % off-diagonal values against MATLAB's eig() function
71 |
72 | % Note that the eigenvectors sometimes differ by a factor of -1
73 | function testSmallOffDiagonalMatrices (testCase)
74 | a = 2000 * rand(1000, 1) - 1000;
75 | b = 2E-2 * rand(1000, 1) - 1E-2;
76 | d = 2000 * rand(1000, 1) - 1000;
77 |
78 | [V1, V2, L1, L2] = eig2x2([a, b, d]);
79 |
80 | for i = 1:1000
81 | [V, D] = eig([a(i), b(i); b(i), d(i)], 'vector');
82 | [D, idx] = sort(D, 'descend');
83 | V = V(:, idx);
84 | testCase.verifyEqual([L1(i); L2(i)], D);
85 | testCase.verifyEqual([V1(i, :)', V2(i, :)'], V);
86 | end
87 | end
88 | end
89 |
90 | end
91 |
92 | % ----------------- END OF CODE -----------------
93 | %
94 | % Please record any changes to the software in the change history
95 | % shown below:
96 | %
97 | %---------------- CHANGE HISTORY ------------------
98 | % Developer | Date | Description
99 | %--------------------------------------------------
100 | % E. White | 07-12-2023 | Initial development
101 |
102 | % =========================================================================
103 | %
104 | % Copyright (c) 2023 United States Government as represented by the
105 | % Administrator of the National Aeronautics and Space Administration.
106 | % All Rights Reserved.
107 | %
108 | % =========================================================================
109 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/FindNearbyCA_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = { ...
2 | matlab.unittest.fixtures.PathFixture('..')}) ...
3 | FindNearbyCA_UnitTest < matlab.unittest.TestCase
4 | %% Series of Tests intended to test the Determination of TCA rectification from if improperly characterized in state estimates
5 | methods (Test)
6 | function test01(testCase) % 25544_conj_44437_20190727_160507_20190727_160507 (dTCA = 497.068 s)
7 | Accuracy = 5E-4;
8 | expSolution = -497.068;
9 | % ID of Test Event
10 | FileName = 'InputFiles/FindNearbyCA_TestCases.mat';
11 | idx = 1;
12 | load(FileName,'DB');
13 |
14 | % Set up Object States
15 | X1 = DB(idx,172:177)';
16 | X2 = DB(idx,178:183)';
17 |
18 | % Calculate Corrected TCA
19 | [actSolution,X1CA,X2CA] = FindNearbyCA(X1,X2);
20 |
21 | % Verify Expected Solution
22 | testCase.verifyEqual(actSolution,expSolution,'AbsTol',Accuracy);
23 | % Verify Output States Correspond to Linear Estimation
24 | testCase.verifyEqual(X1CA,X1+actSolution*[X1(4:6)' 0 0 0]','RelTol',Accuracy);
25 | testCase.verifyEqual(X2CA,X2+actSolution*[X2(4:6)' 0 0 0]','RelTol',Accuracy);
26 | end
27 |
28 | function test02(testCase) % 43042_conj_43043_20190505_061624_20190428_061624 (dTCA = 28.329 s)
29 | Accuracy = 5E-4;
30 | expSolution = 28.329;
31 | % ID of Test Event
32 | FileName = 'InputFiles/FindNearbyCA_TestCases.mat';
33 | idx = 2;
34 | load(FileName,'DB');
35 |
36 | % Set up Object States
37 | X1 = DB(idx,172:177)';
38 | X2 = DB(idx,178:183)';
39 |
40 | % Calculate Corrected TCA
41 | [actSolution,X1CA,X2CA] = FindNearbyCA(X1,X2);
42 |
43 | % Verify Expected Solution
44 | testCase.verifyEqual(actSolution,expSolution,'AbsTol',Accuracy);
45 | % Verify Output States Correspond to Linear Estimation
46 | testCase.verifyEqual(X1CA,X1+actSolution*[X1(4:6)' 0 0 0]','RelTol',Accuracy);
47 | testCase.verifyEqual(X2CA,X2+actSolution*[X2(4:6)' 0 0 0]','RelTol',Accuracy);
48 | end
49 |
50 | function test03(testCase) % 25544_conj_44437_20190803_160507_20190727_160507 (dTCA = 27.198 s)
51 | Accuracy = 5E-4;
52 | expSolution = 27.198;
53 | % ID of Test Event
54 | FileName = 'InputFiles/FindNearbyCA_TestCases.mat';
55 | idx = 3;
56 | load(FileName,'DB');
57 |
58 | % Set up Object States
59 | X1 = DB(idx,172:177)';
60 | X2 = DB(idx,178:183)';
61 |
62 | % Calculate Corrected TCA
63 | [actSolution,X1CA,X2CA] = FindNearbyCA(X1,X2);
64 |
65 | % Verify Expected Solution
66 | testCase.verifyEqual(actSolution,expSolution,'AbsTol',Accuracy);
67 | % Verify Output States Correspond to Linear Estimation
68 | testCase.verifyEqual(X1CA,X1+actSolution*[X1(4:6)' 0 0 0]','RelTol',Accuracy);
69 | testCase.verifyEqual(X2CA,X2+actSolution*[X2(4:6)' 0 0 0]','RelTol',Accuracy);
70 | end
71 |
72 | function test04(testCase) % 26998_conj_81790_20180707_061819_20180707_061819 (dTCA = 0.173 s)
73 | Accuracy = 5E-4;
74 | expSolution = -0.173;
75 | % ID of Test Event
76 | FileName = 'InputFiles/FindNearbyCA_TestCases.mat';
77 | idx = 4;
78 | load(FileName,'DB');
79 |
80 | % Set up Object States
81 | X1 = DB(idx,67:72)';
82 | X2 = DB(idx,127:132)';
83 |
84 | % Calculate Corrected TCA
85 | [actSolution,X1CA,X2CA] = FindNearbyCA(X1,X2);
86 |
87 | % Verify Expected Solution
88 | testCase.verifyEqual(actSolution,expSolution,'AbsTol',Accuracy);
89 | % Verify Output States Correspond to Linear Estimation
90 | testCase.verifyEqual(X1CA,X1+actSolution*[X1(4:6)' 0 0 0]','RelTol',Accuracy);
91 | testCase.verifyEqual(X2CA,X2+actSolution*[X2(4:6)' 0 0 0]','RelTol',Accuracy);
92 | end
93 | end
94 | end
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/management/commands/seed_cdm_data.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | from django.core.management.base import BaseCommand
4 | from api.models import CDM
5 |
6 | class Command(BaseCommand):
7 | help = "Seeds CDM data from JSON files into the database"
8 |
9 | def add_arguments(self, parser):
10 | parser.add_argument(
11 | '--file', type=str, help="Path to the JSON file containing CDM data"
12 | )
13 |
14 | def handle(self, *args, **options):
15 | json_file = options['file']
16 |
17 | if not json_file:
18 | self.stdout.write(self.style.ERROR("Please provide a JSON file path using --file"))
19 | return
20 |
21 | # Check if the file exists
22 | if not os.path.exists(json_file):
23 | self.stdout.write(self.style.ERROR(f"File not found: {json_file}"))
24 | return
25 |
26 | # Load the JSON data
27 | with open(json_file, 'r') as file:
28 | data = json.load(file)
29 |
30 | # Seed data into the database
31 | for item in data:
32 | cdm, created = CDM.objects.update_or_create(
33 | message_id=item['MESSAGE_ID'],
34 | defaults={
35 | "ccsds_cdm_version": item.get("CCSDS_CDM_VERS"),
36 | "creation_date": item.get("CREATION_DATE"),
37 | "originator": item.get("ORIGINATOR"),
38 | "tca": item.get("TCA"),
39 | "miss_distance": float(item.get("MISS_DISTANCE", 0)),
40 |
41 | # Satellite 1 details
42 | "sat1_object": item.get("SAT1_OBJECT"),
43 | "sat1_object_designator": item.get("SAT1_OBJECT_DESIGNATOR"),
44 | "sat1_maneuverable": item.get("SAT1_MANEUVERABLE"),
45 | "sat1_x": float(item.get("SAT1_X", 0)),
46 | "sat1_y": float(item.get("SAT1_Y", 0)),
47 | "sat1_z": float(item.get("SAT1_Z", 0)),
48 | "sat1_x_dot": float(item.get("SAT1_X_DOT", 0)),
49 | "sat1_y_dot": float(item.get("SAT1_Y_DOT", 0)),
50 | "sat1_z_dot": float(item.get("SAT1_Z_DOT", 0)),
51 |
52 | # Covariance matrix for Satellite 1
53 | "sat1_cov_rr": float(item.get("SAT1_CR_R", 0)),
54 | "sat1_cov_rt": float(item.get("SAT1_CT_R", 0)),
55 | "sat1_cov_rn": float(item.get("SAT1_CN_R", 0)),
56 | "sat1_cov_tr": float(item.get("SAT1_CR_T", 0)),
57 | "sat1_cov_tt": float(item.get("SAT1_CT_T", 0)),
58 | "sat1_cov_tn": float(item.get("SAT1_CN_T", 0)),
59 | "sat1_cov_nr": float(item.get("SAT1_CR_N", 0)),
60 | "sat1_cov_nt": float(item.get("SAT1_CT_N", 0)),
61 | "sat1_cov_nn": float(item.get("SAT1_CN_N", 0)),
62 |
63 | # Satellite 2 details
64 | "sat2_object": item.get("SAT2_OBJECT"),
65 | "sat2_object_designator": item.get("SAT2_OBJECT_DESIGNATOR"),
66 | "sat2_maneuverable": item.get("SAT2_MANEUVERABLE"),
67 | "sat2_x": float(item.get("SAT2_X", 0)),
68 | "sat2_y": float(item.get("SAT2_Y", 0)),
69 | "sat2_z": float(item.get("SAT2_Z", 0)),
70 | "sat2_x_dot": float(item.get("SAT2_X_DOT", 0)),
71 | "sat2_y_dot": float(item.get("SAT2_Y_DOT", 0)),
72 | "sat2_z_dot": float(item.get("SAT2_Z_DOT", 0)),
73 |
74 | # Covariance matrix for Satellite 2
75 | "sat2_cov_rr": float(item.get("SAT2_CR_R", 0)),
76 | "sat2_cov_rt": float(item.get("SAT2_CT_R", 0)),
77 | "sat2_cov_rn": float(item.get("SAT2_CN_R", 0)),
78 | "sat2_cov_tr": float(item.get("SAT2_CR_T", 0)),
79 | "sat2_cov_tt": float(item.get("SAT2_CT_T", 0)),
80 | "sat2_cov_tn": float(item.get("SAT2_CN_T", 0)),
81 | "sat2_cov_nr": float(item.get("SAT2_CR_N", 0)),
82 | "sat2_cov_nt": float(item.get("SAT2_CT_N", 0)),
83 | "sat2_cov_nn": float(item.get("SAT2_CN_N", 0)),
84 |
85 | # Hard Body Radius (if present in JSON data)
86 | "hard_body_radius": float(item.get("HBR", 0))
87 | }
88 | )
89 | action = "Created" if created else "Updated"
90 | self.stdout.write(self.style.SUCCESS(f"{action} CDM entry with MESSAGE_ID: {cdm.message_id}"))
91 |
92 | self.stdout.write(self.style.SUCCESS("Data seeding completed."))
93 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/eig2x2.m:
--------------------------------------------------------------------------------
1 | function [V1, V2, L1, L2] = eig2x2(Araw)
2 | % eig2x2 - eigenvalue and eigenvector solver for 2x2 symmetric matrices.
3 | %
4 | % Syntax: [V1, V2, L1, L2] = eig2x2(Araw);
5 | %
6 | % =========================================================================
7 | %
8 | % Copyright (c) 2023 United States Government as represented by the
9 | % Administrator of the National Aeronautics and Space Administration.
10 | % All Rights Reserved.
11 | %
12 | % =========================================================================
13 | %
14 | % Input:
15 | %
16 | % Araw - matrix representing n symmetric matrices [nx3]
17 | % Araw(:,1) is the 1,1 component for each symmetric
18 | % matrix
19 | % Araw(:,2) is the 1,2 and 2,1 component for each
20 | % symmetric matrix
21 | % Araw(:,3) is the 2,2 component for each symmetric
22 | % matrix
23 | %
24 | % =========================================================================
25 | %
26 | % Output:
27 | %
28 | % V1 - matrix representing n of the 1st eigenvectors [nx2]
29 | % V2 - matrix representing n of the 2nd eigenvectors [nx2]
30 | % L1 - matrix representing n of the 1st eigenvalues [nx1]
31 | % L2 - matrix representing n of the 2nd eigenvalues [nx1]
32 | %
33 | % =========================================================================
34 | %
35 | % References:
36 | %
37 | % Martel, Stephen J., "Eigenvectors and eigenvalues of real symmetric
38 | % matrices," June 2016
39 | %
40 | % =========================================================================
41 | %
42 | % Initial version: May 2022; Latest update: Jul 2023
43 | %
44 | % ----------------- BEGIN CODE -----------------
45 |
46 | Nvec = size(Araw,1);
47 |
48 | % Calculate eigenvalues and eigenvectors for each covariance matrix
49 | % A = [a b; c d], with b = c
50 | % with
51 | % a = Amat(:,1); b = Amat(:,2); c = b; d = Amat(:,3);
52 | % Trace: T = a+d
53 | % Determinant: D = a*d-b*c
54 | T = Araw(:,1)+Araw(:,3);
55 | D = Araw(:,1).*Araw(:,3)-Araw(:,2).^2;
56 |
57 | L1 = (T + sqrt(T .^ 2 - 4 .* D)) ./ 2; % Largest eigenvalue
58 | L2 = (T - sqrt(T .^ 2 - 4 .* D)) ./ 2; % Smallest eigenvalue
59 |
60 | % Initialize the conjunction plane eigenvector arrays
61 | V1 = NaN([Nvec 2]);
62 | V2 = NaN([Nvec 2]);
63 |
64 | % Eigenvectors for the subset of covariance matrices that have
65 | % non-zero off-diagonal values
66 | c0 = Araw(:,2) ~= 0;
67 | V1(c0,1) = L1(c0)-Araw(c0,3);
68 | V2(c0,1) = L2(c0)-Araw(c0,3);
69 | V1(c0,2) = Araw(c0,2);
70 | V2(c0,2) = Araw(c0,2);
71 | V1(c0,:) = V1(c0,:)./repmat(sqrt(V1(c0,1).^2+V1(c0,2).^2),[1 2]);
72 | V2(c0,:) = V2(c0,:)./repmat(sqrt(V2(c0,1).^2+V2(c0,2).^2),[1 2]);
73 |
74 | % Eigenvectors for A matrices with zero off-diagonal values
75 | c0 = ~c0;
76 | V1(c0,1) = 1; V2(c0,1) = 0;
77 | V1(c0,2) = 0; V2(c0,2) = 1;
78 |
79 | % Final special check, check for "b" values that are close to 0, but
80 | % aren't exactly 0. Run eigenvalue decomposition manually since this
81 | % algorithm starts to break down with abs(b) < 1e-2
82 | c0 = abs(Araw(:,2)) < 1e-2 & Araw(:,2) ~= 0;
83 | if sum(c0) > 0
84 | fix = find(c0);
85 | for i = 1:length(fix)
86 | idx = fix(i);
87 | tempA = [Araw(idx,1) Araw(idx,2); Araw(idx,2) Araw(idx,3)];
88 | [V, D] = eig(tempA, 'vector');
89 | [D, ind] = sort(D);
90 | V = V(:, ind);
91 | V2(idx,:) = V(:,1)';
92 | V1(idx,:) = V(:,2)';
93 | L2(idx) = D(1);
94 | L1(idx) = D(2);
95 | end
96 | end
97 | end
98 |
99 | % ----------------- END OF CODE -----------------
100 | %
101 | % Please record any changes to the software in the change history
102 | % shown below:
103 | %
104 | % ----------------- CHANGE HISTORY ------------------
105 | % Developer | Date | Description
106 | % ---------------------------------------------------
107 | % L. Baars | 05-19-2022 | Initial development
108 | % E. White | 07-12-2023 | Added compliant documentation, fixed one
109 | % instance of division to be componentwise
110 |
111 | % =========================================================================
112 | %
113 | % Copyright (c) 2023 United States Government as represented by the
114 | % Administrator of the National Aeronautics and Space Administration.
115 | % All Rights Reserved.
116 | %
117 | % =========================================================================
118 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/models/cdm.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | class CDM(models.Model):
4 | # Basic Metadata
5 | ccsds_cdm_version = models.CharField(max_length=10)
6 | creation_date = models.DateTimeField()
7 | originator = models.CharField(max_length=50)
8 | message_id = models.CharField(max_length=100, unique=True)
9 | privacy = models.BooleanField(default=True)
10 |
11 | # Conjunction Details
12 | tca = models.DateTimeField() # Time of Closest Approach
13 | miss_distance = models.FloatField() # Minimum distance between the satellites (meters)
14 |
15 | # Satellite 1 Details
16 | sat1_object = models.CharField(max_length=50)
17 | sat1_object_designator = models.CharField(max_length=50)
18 | sat1_maneuverable = models.CharField(max_length=3) # "YES" or "NO"
19 | sat1_x = models.FloatField() # X position
20 | sat1_y = models.FloatField() # Y position
21 | sat1_z = models.FloatField() # Z position
22 | sat1_x_dot = models.FloatField() # X velocity
23 | sat1_y_dot = models.FloatField() # Y velocity
24 | sat1_z_dot = models.FloatField() # Z velocity
25 |
26 | # Covariance matrix elements for Satellite 1
27 | sat1_cov_rr = models.FloatField(null=True, blank=True) # Covariance rr element
28 | sat1_cov_rt = models.FloatField(null=True, blank=True) # Covariance rt element
29 | sat1_cov_rn = models.FloatField(null=True, blank=True) # Covariance rn element
30 | sat1_cov_tr = models.FloatField(null=True, blank=True) # Covariance tr element
31 | sat1_cov_tt = models.FloatField(null=True, blank=True) # Covariance tt element
32 | sat1_cov_tn = models.FloatField(null=True, blank=True) # Covariance tn element
33 | sat1_cov_nr = models.FloatField(null=True, blank=True) # Covariance nr element
34 | sat1_cov_nt = models.FloatField(null=True, blank=True) # Covariance nt element
35 | sat1_cov_nn = models.FloatField(null=True, blank=True) # Covariance nn element
36 |
37 | # Additional Satellite 1 Details
38 | sat1_catalog_name = models.CharField(max_length=100, null=True, blank=True)
39 | sat1_object_name = models.CharField(max_length=100, null=True, blank=True)
40 | sat1_international_designator = models.CharField(max_length=100, null=True, blank=True)
41 | sat1_object_type = models.CharField(max_length=100, null=True, blank=True)
42 | sat1_operator_organization = models.CharField(max_length=100, null=True, blank=True)
43 | sat1_covariance_method = models.CharField(max_length=100, null=True, blank=True)
44 | sat1_reference_frame = models.CharField(max_length=100, null=True, blank=True)
45 |
46 | # Satellite 2 Details
47 | sat2_object = models.CharField(max_length=50)
48 | sat2_object_designator = models.CharField(max_length=50)
49 | sat2_maneuverable = models.CharField(max_length=3) # "YES" or "NO"
50 | sat2_x = models.FloatField() # X position
51 | sat2_y = models.FloatField() # Y position
52 | sat2_z = models.FloatField() # Z position
53 | sat2_x_dot = models.FloatField() # X velocity
54 | sat2_y_dot = models.FloatField() # Y velocity
55 | sat2_z_dot = models.FloatField() # Z velocity
56 |
57 | # Additional Satellite 2 Details
58 | sat2_catalog_name = models.CharField(max_length=100, null=True, blank=True)
59 | sat2_object_name = models.CharField(max_length=100, null=True, blank=True)
60 | sat2_international_designator = models.CharField(max_length=100, null=True, blank=True)
61 | sat2_object_type = models.CharField(max_length=100, null=True, blank=True)
62 | sat2_operator_organization = models.CharField(max_length=100, null=True, blank=True)
63 | sat2_covariance_method = models.CharField(max_length=100, null=True, blank=True)
64 | sat2_reference_frame = models.CharField(max_length=100, null=True, blank=True)
65 |
66 | # Covariance matrix elements for Satellite 2
67 | sat2_cov_rr = models.FloatField(null=True, blank=True) # Covariance rr element
68 | sat2_cov_rt = models.FloatField(null=True, blank=True) # Covariance rt element
69 | sat2_cov_rn = models.FloatField(null=True, blank=True) # Covariance rn element
70 | sat2_cov_tr = models.FloatField(null=True, blank=True) # Covariance tr element
71 | sat2_cov_tt = models.FloatField(null=True, blank=True) # Covariance tt element
72 | sat2_cov_tn = models.FloatField(null=True, blank=True) # Covariance tn element
73 | sat2_cov_nr = models.FloatField(null=True, blank=True) # Covariance nr element
74 | sat2_cov_nt = models.FloatField(null=True, blank=True) # Covariance nt element
75 | sat2_cov_nn = models.FloatField(null=True, blank=True) # Covariance nn element
76 |
77 | # Hard Body Radius
78 | hard_body_radius = models.FloatField(default=20) # Hard Body Radius (HBR)
79 |
80 | def __str__(self):
81 | return f"CDM {self.message_id} between {self.sat1_object} and {self.sat2_object}"
82 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/orbit_predictor/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for orbit_predictor project.
3 |
4 | Generated by 'django-admin startproject' using Django 5.1.1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/5.1/ref/settings/
11 | """
12 |
13 | import datetime
14 | from pathlib import Path
15 | import os
16 | from dotenv import load_dotenv
17 |
18 | # Load environment variables
19 | load_dotenv()
20 |
21 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
22 | BASE_DIR = Path(__file__).resolve().parent.parent
23 |
24 |
25 | # Quick-start development settings - unsuitable for production
26 | # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
27 |
28 | # SECURITY WARNING: keep the secret key used in production secret!
29 | SECRET_KEY = 'django-insecure-pqk=*u1b^pjq)n(%y6iq6+8c4=9ny)1m7(+de9^niq^!6m$u!-'
30 |
31 | # SECURITY WARNING: don't run with debug turned on in production!
32 | DEBUG = True
33 |
34 | ALLOWED_HOSTS = []
35 |
36 |
37 | # Application definition
38 |
39 | INSTALLED_APPS = [
40 | # 'django.contrib.admin',
41 | 'django.contrib.auth',
42 | 'django.contrib.contenttypes',
43 | # 'django.contrib.sessions',
44 | # 'django.contrib.messages',
45 | 'django.contrib.staticfiles',
46 | 'rest_framework',
47 | 'django_filters', # Added for filtering
48 | 'corsheaders', # Added for handling CORS
49 | 'api',
50 | ]
51 |
52 | MIDDLEWARE = [
53 | 'django.middleware.security.SecurityMiddleware',
54 | 'corsheaders.middleware.CorsMiddleware', # Must be placed near the top
55 | # 'django.contrib.sessions.middleware.SessionMiddleware',
56 | 'django.middleware.common.CommonMiddleware',
57 | # 'django.middleware.csrf.CsrfViewMiddleware',
58 | # 'django.contrib.auth.middleware.AuthenticationMiddleware',
59 | # 'django.contrib.messages.middleware.MessageMiddleware',
60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
61 | ]
62 |
63 | ROOT_URLCONF = 'orbit_predictor.urls'
64 |
65 | TEMPLATES = [
66 | {
67 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
68 | 'DIRS': [],
69 | 'APP_DIRS': True,
70 | 'OPTIONS': {
71 | 'context_processors': [
72 | 'django.template.context_processors.debug',
73 | 'django.template.context_processors.request',
74 | # 'django.contrib.auth.context_processors.auth',
75 | # 'django.contrib.messages.context_processors.messages',
76 | ],
77 | },
78 | },
79 | ]
80 |
81 | WSGI_APPLICATION = 'orbit_predictor.wsgi.application'
82 |
83 |
84 | # Database Configuration
85 | DATABASES = {
86 | 'default': {
87 | 'ENGINE': 'django.db.backends.postgresql',
88 | 'NAME': os.getenv('SUPABASE_DB_NAME'),
89 | 'USER': os.getenv('SUPABASE_DB_USER'),
90 | 'PASSWORD': os.getenv('SUPABASE_DB_PASSWORD'),
91 | 'HOST': os.getenv('SUPABASE_DB_HOST'),
92 | 'PORT': os.getenv('SUPABASE_DB_PORT'),
93 | }
94 | }
95 |
96 |
97 | ADMIN_REGISTRATION_CODE = os.getenv('ADMIN_REGISTRATION_CODE')
98 |
99 | # REST Framework Configuration
100 | REST_FRAMEWORK = {
101 | 'DEFAULT_AUTHENTICATION_CLASSES': (
102 | 'api.authentication.JWTAuthentication', # Your custom JWT Authentication
103 | ),
104 | 'DEFAULT_PERMISSION_CLASSES': (
105 | 'rest_framework.permissions.IsAuthenticated', # Default to authenticated
106 | ),
107 | 'DEFAULT_FILTER_BACKENDS': (
108 | 'django_filters.rest_framework.DjangoFilterBackend',
109 | ),
110 | # Override these defaults to prevent DRF from using Django's auth system
111 | 'UNAUTHENTICATED_USER': None,
112 | 'UNAUTHENTICATED_TOKEN': None,
113 | }
114 |
115 | JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY')
116 | JWT_ACCESS_EXPIRATION_DELTA = datetime.timedelta(hours=24) # Access token valid for 24 hours
117 | JWT_REFRESH_EXPIRATION_DELTA = datetime.timedelta(days=7) # Refresh token valid for 7 days
118 |
119 | AUTH_USER_MODEL = 'api.User'
120 |
121 |
122 | # Internationalization
123 | LANGUAGE_CODE = 'en-us'
124 |
125 | TIME_ZONE = 'UTC'
126 |
127 | USE_I18N = True
128 |
129 | USE_TZ = True
130 |
131 |
132 | # Static files (CSS, JavaScript, Images)
133 | STATIC_URL = 'static/'
134 |
135 | # Default primary key field type
136 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
137 |
138 | # Email Configuration
139 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
140 | EMAIL_HOST = 'smtp.gmail.com'
141 | EMAIL_PORT = 587
142 | EMAIL_USE_TLS = True
143 | EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
144 | EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "").replace('\xa0', '')
145 |
146 |
147 | # CORS configuration: Allow requests from http://localhost:3000
148 | CORS_ALLOWED_ORIGINS = [
149 | "http://localhost:3000",
150 | ]
151 |
152 | # Alternatively, for development purposes you could use:
153 | # CORS_ALLOW_ALL_ORIGINS = True
154 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/get_covXcorr_parameters.m:
--------------------------------------------------------------------------------
1 | function [covXcorr,sigp,Gvecp,sigs,Gvecs] = get_covXcorr_parameters(params)
2 | % get_covXcorr_parameters - Gets covariance cross-correlation parameters
3 | % from the parameter structure passed in.
4 | %
5 | % Syntax: [covXcorr,sigp,Gvecp,sigs,Gvecs] = get_covXcorr_parameters(params)
6 | %
7 | % =========================================================================
8 | %
9 | % Copyright (c) 2023 United States Government as represented by the
10 | % Administrator of the National Aeronautics and Space Administration.
11 | % All Rights Reserved.
12 | %
13 | % =========================================================================
14 | %
15 | % Description:
16 | %
17 | % This function retreives covariance cross-correlation parameters from
18 | % the parameters structure passed in. Casali (2018) describes the
19 | % parameters used to calculate covariance cross-correlation effects.
20 | %
21 | % =========================================================================
22 | %
23 | % Input:
24 | %
25 | % params = Matlab structure with the following fields:
26 | %
27 | % params.covXcorr = Matlab structure with the following fields:
28 | %
29 | % params.covXcorr.sigp = DCP sigma for the primary object
30 | %
31 | % params.covXcorr.sigs = DCP sigma for the secondary object
32 | %
33 | % params.covXcorr.Gvecp = 1x6 DCP sensitivity vector for the primary
34 | % object
35 | %
36 | % params.covXcorr.Gvecs = 1x6 DCP sensitivity vector for the
37 | % secondary object
38 | %
39 | % =========================================================================
40 | %
41 | % Output:
42 | %
43 | % covXcorr = true/false indicator of whether or not cross-correlation
44 | % information was correctly read
45 | %
46 | % sigp = DCP sigma for the primary object
47 | %
48 | % Gvecp = 1x6 DCP sensitivity vector for the primary object
49 | %
50 | % sigs = DCP sigma for the secondary object
51 | %
52 | % Gvecs = 1x6 DCP sensitivity vector for the secondary object
53 | %
54 | % =========================================================================
55 | %
56 | % References:
57 | %
58 | % Casali, S. J., et. al. (2018) "Effect of Cross-Correlation of
59 | % Orbital Error on Probability of Collision Determination" AAS 18-272.
60 | %
61 | % =========================================================================
62 | %
63 | % Initial version: Jan 2020; Latest update: Mar 2023
64 | %
65 | % ----------------- BEGIN CODE -----------------
66 |
67 | % Extract and check covariance cross correlation DCP parameters
68 |
69 | % Initialize cross correlation processing flag to false, and only change to
70 | % true if valid covXcorr parameters are found in parameters structure
71 | covXcorr = false;
72 |
73 | % Initialize output DCP sigma values and sensitivity vectors
74 | sigp = []; Gvecp = []; sigs = []; Gvecs = [];
75 |
76 | % Check for valid covXcorr parameters
77 |
78 | if isfield(params,'covXcorr') && ~isempty(params.covXcorr)
79 |
80 | % Extract DCP sigma values
81 | if isfield(params.covXcorr,'sigp'); sigp = params.covXcorr.sigp; end
82 | if isfield(params.covXcorr,'sigs'); sigs = params.covXcorr.sigs; end
83 |
84 | % Extract DCP sensitivity vectors
85 | if isfield(params.covXcorr,'Gvecp'); Gvecp = params.covXcorr.Gvecp; end
86 | if isfield(params.covXcorr,'Gvecs'); Gvecs = params.covXcorr.Gvecs; end
87 |
88 | % Return with false covXcorr flag if any DCP quantities are empty
89 | if isempty(sigp) || isempty(Gvecp) || ...
90 | isempty(sigs) || isempty(Gvecs)
91 | return;
92 | end
93 |
94 | % Check for correct dimensions
95 | if ~isequal(size(sigp) ,[1 1]) || ~isequal(size(sigs) ,[1 1]) || ...
96 | ~isequal(size(Gvecp),[1 6]) || ~isequal(size(Gvecs),[1 6])
97 | error('Incorrect DCP value dimensions');
98 | end
99 |
100 | % Check for invalid DCP values
101 | if isnan(sigp) || (sigp < 0) || ...
102 | isnan(sigs) || (sigs < 0)
103 | error('Invalid DCP sigma value(s) found');
104 | end
105 | if any(isnan(Gvecp)) || any(isnan(Gvecs))
106 | error('Invalid DCP sensitivity vector value(s) found');
107 | end
108 |
109 | % At this point, all checks have been passed so set the covariance
110 | % cross correlation processing flag to true
111 | covXcorr = true;
112 |
113 | end
114 |
115 | return
116 | end
117 |
118 | % ----------------- END OF CODE ------------------
119 | %
120 | % Please record any changes to the software in the change history
121 | % shown below:
122 | %
123 | % ----------------- CHANGE HISTORY ------------------
124 | % Developer | Date | Description
125 | % ---------------------------------------------------
126 | % L. Baars | 2023-MAR-10 | Added header/footer/copyright infomration
127 | % to the existing function.
128 |
129 | % =========================================================================
130 | %
131 | % Copyright (c) 2023 United States Government as represented by the
132 | % Administrator of the National Aeronautics and Space Administration.
133 | % All Rights Reserved.
134 | %
135 | % =========================================================================
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.3 on 2024-12-05 21:46
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='CDM',
17 | fields=[
18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('ccsds_cdm_version', models.CharField(max_length=10)),
20 | ('creation_date', models.DateTimeField()),
21 | ('originator', models.CharField(max_length=50)),
22 | ('message_id', models.CharField(max_length=100, unique=True)),
23 | ('tca', models.DateTimeField()),
24 | ('miss_distance', models.FloatField()),
25 | ('sat1_object', models.CharField(max_length=50)),
26 | ('sat1_object_designator', models.CharField(max_length=50)),
27 | ('sat1_maneuverable', models.CharField(max_length=3)),
28 | ('sat1_x', models.FloatField()),
29 | ('sat1_y', models.FloatField()),
30 | ('sat1_z', models.FloatField()),
31 | ('sat1_x_dot', models.FloatField()),
32 | ('sat1_y_dot', models.FloatField()),
33 | ('sat1_z_dot', models.FloatField()),
34 | ('sat1_cov_rr', models.FloatField(blank=True, null=True)),
35 | ('sat1_cov_rt', models.FloatField(blank=True, null=True)),
36 | ('sat1_cov_rn', models.FloatField(blank=True, null=True)),
37 | ('sat1_cov_tr', models.FloatField(blank=True, null=True)),
38 | ('sat1_cov_tt', models.FloatField(blank=True, null=True)),
39 | ('sat1_cov_tn', models.FloatField(blank=True, null=True)),
40 | ('sat1_cov_nr', models.FloatField(blank=True, null=True)),
41 | ('sat1_cov_nt', models.FloatField(blank=True, null=True)),
42 | ('sat1_cov_nn', models.FloatField(blank=True, null=True)),
43 | ('sat2_object', models.CharField(max_length=50)),
44 | ('sat2_object_designator', models.CharField(max_length=50)),
45 | ('sat2_maneuverable', models.CharField(max_length=3)),
46 | ('sat2_x', models.FloatField()),
47 | ('sat2_y', models.FloatField()),
48 | ('sat2_z', models.FloatField()),
49 | ('sat2_x_dot', models.FloatField()),
50 | ('sat2_y_dot', models.FloatField()),
51 | ('sat2_z_dot', models.FloatField()),
52 | ('sat2_cov_rr', models.FloatField(blank=True, null=True)),
53 | ('sat2_cov_rt', models.FloatField(blank=True, null=True)),
54 | ('sat2_cov_rn', models.FloatField(blank=True, null=True)),
55 | ('sat2_cov_tr', models.FloatField(blank=True, null=True)),
56 | ('sat2_cov_tt', models.FloatField(blank=True, null=True)),
57 | ('sat2_cov_tn', models.FloatField(blank=True, null=True)),
58 | ('sat2_cov_nr', models.FloatField(blank=True, null=True)),
59 | ('sat2_cov_nt', models.FloatField(blank=True, null=True)),
60 | ('sat2_cov_nn', models.FloatField(blank=True, null=True)),
61 | ('hard_body_radius', models.FloatField(default=20)),
62 | ],
63 | ),
64 | migrations.CreateModel(
65 | name='Conjunction',
66 | fields=[
67 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
68 | ('date', models.DateTimeField()),
69 | ('risk_factor', models.FloatField()),
70 | ('description', models.TextField()),
71 | ('sat1_object_designator', models.CharField(max_length=50)),
72 | ('sat2_object_designator', models.CharField(max_length=50)),
73 | ('cdm_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conjunctions', to='api.cdm')),
74 | ],
75 | ),
76 | migrations.CreateModel(
77 | name='Collision',
78 | fields=[
79 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
80 | ('collision_date', models.DateTimeField()),
81 | ('risk_factor', models.FloatField()),
82 | ('sat1_object_designator', models.CharField(max_length=50)),
83 | ('sat2_object_designator', models.CharField(max_length=50)),
84 | ('conjunction_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='collisions', to='api.conjunction')),
85 | ],
86 | ),
87 | migrations.CreateModel(
88 | name='ProbabilityCalc',
89 | fields=[
90 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
91 | ('probability_value', models.FloatField()),
92 | ('time_to_impact', models.DurationField()),
93 | ('conjuction_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='probability_calcs', to='api.conjunction')),
94 | ],
95 | ),
96 | ]
97 |
--------------------------------------------------------------------------------
/on-orbit-frontend/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap');
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | /* @media (prefers-color-scheme: dark) {
7 | :root {
8 | --background: #0a0a0a;
9 | --foreground: #ededed;
10 | }
11 | } */
12 |
13 | body {
14 | font-family: 'Outfit', 'Arial', 'Helvetica', 'sans-serif';
15 | }
16 |
17 |
18 |
19 | @layer base {
20 | :root {
21 | --background: 0 0% 100%;
22 | --foreground: 222.2 84% 4.9%;
23 | --card: 0 0% 100%;
24 | --card-foreground: 222.2 84% 4.9%;
25 | --popover: 0 0% 100%;
26 | --popover-foreground: 222.2 84% 4.9%;
27 | --primary: 354 85.4% 51.8%;
28 | --primary-foreground: 210 40% 98%;
29 | --secondary: 210 40% 96.1%;
30 | --secondary-foreground: 222.2 47.4% 11.2%;
31 | --muted: 210 40% 96.1%;
32 | --muted-foreground: 215.4 16.3% 46.9%;
33 | --accent: 210 40% 96.1%;
34 | --accent-foreground: 222.2 47.4% 11.2%;
35 | --destructive: 0 84.2% 60.2%;
36 | --destructive-foreground: 210 40% 98%;
37 | --border: 214.3 31.8% 91.4%;
38 | --input: 214.3 31.8% 91.4%;
39 | --ring: 222.2 84% 4.9%;
40 | --chart-1: 12 76% 61%;
41 | --chart-2: 173 58% 39%;
42 | --chart-3: 197 37% 24%;
43 | --chart-4: 43 74% 66%;
44 | --chart-5: 27 87% 67%;
45 | --radius: 0.5rem;
46 | }
47 | .dark {
48 | --background: 222.2 84% 4.9%;
49 | --foreground: 210 40% 98%;
50 | --card: 222.2 84% 4.9%;
51 | --card-foreground: 210 40% 98%;
52 | --popover: 222.2 84% 4.9%;
53 | --popover-foreground: 210 40% 98%;
54 | --primary: 210 40% 98%;
55 | --primary-foreground: 222.2 47.4% 11.2%;
56 | --secondary: 217.2 32.6% 17.5%;
57 | --secondary-foreground: 210 40% 98%;
58 | --muted: 217.2 32.6% 17.5%;
59 | --muted-foreground: 215 20.2% 65.1%;
60 | --accent: 217.2 32.6% 17.5%;
61 | --accent-foreground: 210 40% 98%;
62 | --destructive: 0 62.8% 30.6%;
63 | --destructive-foreground: 210 40% 98%;
64 | --border: 217.2 32.6% 17.5%;
65 | --input: 217.2 32.6% 17.5%;
66 | --ring: 212.7 26.8% 83.9%;
67 | --chart-1: 220 70% 50%;
68 | --chart-2: 160 60% 45%;
69 | --chart-3: 30 80% 55%;
70 | --chart-4: 280 65% 60%;
71 | --chart-5: 340 75% 55%;
72 | }
73 | }
74 |
75 |
76 |
77 | @layer base {
78 | * {
79 | @apply border-border;
80 | }
81 | body {
82 | @apply bg-background text-foreground;
83 | }
84 | }
85 |
86 |
87 | .button {
88 | cursor: pointer;
89 | border: none;
90 | background: #473198;
91 | color: white;
92 | width: 100px;
93 | height: 100px;
94 | border-radius: 50%;
95 | overflow: hidden;
96 | position: relative;
97 | display: grid;
98 | place-content: center;
99 | transition:
100 | background 300ms,
101 | transform 200ms;
102 | font-weight: 600;
103 | }
104 |
105 | .button__text {
106 | position: absolute;
107 | inset: 0;
108 | animation: text-rotation 8s linear infinite;
109 |
110 | > span {
111 | position: absolute;
112 | transform: rotate(calc(19deg * var(--index)));
113 | inset: 0px;
114 | }
115 | }
116 |
117 | .button__circle {
118 | position: relative;
119 | width: 40px;
120 | height: 40px;
121 | overflow: hidden;
122 | background: white;
123 | color: #473198;
124 | border-radius: 50%;
125 | display: flex;
126 | align-items: center;
127 | justify-content: center;
128 | }
129 |
130 | .button__icon--copy {
131 | position: absolute;
132 | transform: translate(-150%, 150%);
133 | }
134 |
135 | .button:hover {
136 | background: #000;
137 | transform: scale(1.05);
138 | }
139 |
140 | .button:hover .button__icon {
141 | color: #000;
142 | }
143 |
144 | .button:hover .button__icon:first-child {
145 | transition: transform 0.3s ease-in-out;
146 | transform: translate(150%, -150%);
147 | }
148 |
149 | .button:hover .button__icon--copy {
150 | transition: transform 0.3s ease-in-out 0.1s;
151 | transform: translate(0);
152 | }
153 |
154 | @keyframes text-rotation {
155 | to {
156 | rotate: 360deg;
157 | }
158 | }
159 |
160 |
161 | /* loader */
162 | .spinner {
163 | width: 44px;
164 | height: 44px;
165 | animation: spinner-y0fdc1 2s infinite ease;
166 | transform-style: preserve-3d;
167 | }
168 |
169 | .spinner > div {
170 | background-color: rgba(0,77,255,0.2);
171 | height: 100%;
172 | position: absolute;
173 | width: 100%;
174 | border: 2px solid #004dff;
175 | }
176 |
177 | .spinner div:nth-of-type(1) {
178 | transform: translateZ(-22px) rotateY(180deg);
179 | }
180 |
181 | .spinner div:nth-of-type(2) {
182 | transform: rotateY(-270deg) translateX(50%);
183 | transform-origin: top right;
184 | }
185 |
186 | .spinner div:nth-of-type(3) {
187 | transform: rotateY(270deg) translateX(-50%);
188 | transform-origin: center left;
189 | }
190 |
191 | .spinner div:nth-of-type(4) {
192 | transform: rotateX(90deg) translateY(-50%);
193 | transform-origin: top center;
194 | }
195 |
196 | .spinner div:nth-of-type(5) {
197 | transform: rotateX(-90deg) translateY(50%);
198 | transform-origin: bottom center;
199 | }
200 |
201 | .spinner div:nth-of-type(6) {
202 | transform: translateZ(22px);
203 | }
204 |
205 | @keyframes spinner-y0fdc1 {
206 | 0% {
207 | transform: rotate(45deg) rotateX(-25deg) rotateY(25deg);
208 | }
209 |
210 | 50% {
211 | transform: rotate(45deg) rotateX(-385deg) rotateY(25deg);
212 | }
213 |
214 | 100% {
215 | transform: rotate(45deg) rotateX(-385deg) rotateY(385deg);
216 | }
217 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to the On-Orbit Collision Predictor! 🚀
2 |
3 | In this project, our team was commissioned by the Canadian Space Agency to develop a predictive system for on-orbit satellite collision risks. This tool helps assess potential orbital collisions using a blend of machine learning and advanced statistical models. The system allows users to input satellite data, calculate collision probabilities, and manage prediction reports. It's designed for space agencies, satellite operators, and researchers to improve decision-making and avoid costly or dangerous on-orbit collisions.
4 |
5 | ## 🧠 The Team:
6 |
7 | | Member | Position | Responsibilities |
8 | | ----------------- | ------------------ | ---------------------------------- |
9 | | **Erik Cupsa** | Full Stack + ML | Authentication|
10 | | **Yassine Mimet** | Back End | Collision Calculations, Data Processing |
11 | | **Wasif Somji** | Full Stack | API Development, Backend Architecture|
12 | | **Masa Kagami** | Front End + ML | UI/UX design + Highcharts integration|
13 |
14 | ## 🚀 Key Features
15 |
16 | ### User Accounts
17 | - **Registration & Login**: Users can create secure accounts to access the system.
18 | - **Profile Management**: Users can update profile details and manage their account.
19 |
20 | ### Collision Prediction Functionality
21 | - **Data Input**: Upload satellite information for collision risk assessments.
22 | - **Prediction Results**: Generate collision predictions based on machine learning models.
23 | - **Reports**: Save and manage prediction reports for further analysis.
24 |
25 | ### Admin Controls
26 | - **User Management**: Admins can manage user accounts, including role assignments.
27 | - **System Monitoring**: Admins can monitor prediction usage and system performance.
28 |
29 | ## 🛠️ Tech Stack
30 |
31 | - **Backend**: Django
32 | - **Machine Learning**: MATLAB for initial calculations, Python (scikit-learn) for machine learning model development
33 | - **Frontend**: Next.js with D3 for 3D visualization
34 | - **Database**: PostgreSQL hosted on Supabase
35 |
36 | ## 📂 Project Structure
37 |
38 | ```plaintext
39 | On-Orbit-Collision-Predictor/
40 | │
41 | ├── on-orbit-frontend/ # Next.js frontend
42 | │
43 | ├── Orbit_Predictor-BackEnd/ # Django backend
44 | │ ├── api/ # Django app with models, views, serializers, and URLs
45 | │ └── orbit_predictor/ # Main project configuration files
46 | │
47 | ├── env/ # Python virtual environment
48 | │
49 | └── README.md # Project README
50 | ```
51 |
52 | ## 🚀 Getting Started
53 |
54 | ### Prerequisites
55 |
56 | - **Python 3.10+** (but less than 3.13) and **Django** for the backend to run **MATLAB**
57 | - **Node.js** and **npm** for the Next.js frontend
58 | - **MATLAB** for initial prediction calculations (optional for extended functionality)
59 | - **PostgreSQL** for database management
60 | - **Supabase** for hosted database setup
61 |
62 | ### Setup
63 |
64 | 1. **Clone the Repository**
65 |
66 | ```bash
67 | git clone https://github.com/Erik-Cupsa/On-Orbit-Collision-Predictor.git
68 | cd on-orbit-collision-predictor
69 | ```
70 |
71 | 2. **Install Dependencies**
72 |
73 | - **Backend**: Set up and activate the virtual environment, then install Django and other requirements.
74 |
75 | ```bash
76 | python3 -m venv env
77 | source env/bin/activate
78 | pip install -r requirements.txt
79 | ```
80 |
81 | - **Frontend**: Navigate to the `on-orbit-frontend` folder and install dependencies.
82 |
83 | ```bash
84 | cd on-orbit-frontend
85 | npm install
86 | ```
87 |
88 | 3. **Database Setup** (in progress)
89 |
90 | We're using Supabase for this. Configure the database settings in your .env file. Reference the .env.example file if needed.
91 |
92 | 4. **Inputting CDMs**
93 |
94 | In order to input CDMs into the DB, you can use a configured endpoint and send in the CDM data as a JSON object. Here's how:
95 |
96 | Assuming your backend is running on port `8000`:
97 | Send a request to `http://localhost:8000/api/cdms/create/` with your CDM json object. Example:
98 |
99 | `{
100 | "CCSDS_CDM_VERS": "{{version}}",
101 | "CREATION_DATE": "{{creation_date}}",
102 | "ORIGINATOR": "{{originator}}",
103 | "MESSAGE_ID": "{{message_id}}",
104 | "TCA": "{{time_of_closest_approach}}",
105 | "MISS_DISTANCE": "{{miss_distance}}",
106 | "COLLISION_PROBABILITY": "{{collision_probability}}",
107 | "SAT1_OBJECT": "{{sat1_object}}",
108 | "SAT1_OBJECT_DESIGNATOR": "{{sat1_designator}}",
109 | "SAT1_CATALOG_NAME": "{{sat1_catalog_name}}",
110 | "SAT1_OBJECT_NAME": "{{sat1_object_name}}",
111 | "SAT1_INTERNATIONAL_DESIGNATOR": "{{sat1_intl_designator}}",
112 | "SAT1_OBJECT_TYPE": "{{sat1_object_type}}",
113 | "SAT1_OPERATOR_ORGANIZATION": "{{sat1_operator_org}}",
114 | "SAT1_COVARIANCE_METHOD": "{{sat1_covariance_method}}",
115 | "SAT1_MANEUVERABLE": "{{sat1_maneuverable}}",
116 | "SAT1_REFERENCE_FRAME": "{{sat1_reference_frame}}",
117 | "SAT1_X": "{{sat1_x}}",
118 | "SAT1_Y": "{{sat1_y}}"
119 | // continue on with rest of fields
120 | }`
121 |
122 |
123 | 6. **Run DB Migrations**
124 |
125 | If you modify the schema:
126 |
127 | ```bash
128 | cd Orbit_Predictor-BackEnd
129 | python manage.py makemigrations
130 | python manage.py migrate
131 | ```
132 |
133 | ### Running the Project
134 |
135 | To run both the Django backend and the Next.js frontend concurrently:
136 |
137 | ```bash
138 | npm run dev
139 | ```
140 |
141 | This command will start:
142 | - **Next.js frontend** at `http://localhost:3000`
143 | - **Django backend** at `http://localhost:8000`
144 |
145 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/views/user_views.py:
--------------------------------------------------------------------------------
1 |
2 | from rest_framework import generics, status, viewsets, permissions
3 | from rest_framework.response import Response
4 | from rest_framework.permissions import AllowAny
5 | from ..permissions import IsAdmin, IsCollisionAnalyst, IsUser, CanViewCDM
6 | from ..serializers import (
7 | UserSerializer,
8 | LoginSerializer,
9 | CDMSerializer,
10 | RefreshTokenSerializer
11 | )
12 | from ..models import User, CDM
13 | from rest_framework.views import APIView
14 |
15 | class RegisterView(generics.CreateAPIView):
16 | serializer_class = UserSerializer
17 | permission_classes = [AllowAny] # Public access
18 |
19 | def post(self, request, *args, **kwargs):
20 | email = request.data.get('email')
21 | if User.objects.filter(email=email).exists():
22 | return Response(
23 | {'email': 'A user with this email already exists.'},
24 | status=status.HTTP_400_BAD_REQUEST
25 | )
26 |
27 | serializer = self.get_serializer(data=request.data)
28 | if serializer.is_valid():
29 | user = serializer.save()
30 | return Response({
31 | 'id': user.id,
32 | 'email': user.email,
33 | 'role': user.role,
34 | 'created_at': user.created_at
35 | }, status=status.HTTP_201_CREATED)
36 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
37 |
38 |
39 | class LoginView(generics.GenericAPIView):
40 | serializer_class = LoginSerializer
41 | permission_classes = [AllowAny] # Public access
42 |
43 | def post(self, request, *args, **kwargs):
44 | serializer = self.get_serializer(data=request.data)
45 | if serializer.is_valid():
46 | return Response(serializer.validated_data, status=status.HTTP_200_OK)
47 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
48 |
49 |
50 | class RefreshTokenView(generics.GenericAPIView):
51 | serializer_class = RefreshTokenSerializer
52 | permission_classes = [AllowAny]
53 |
54 | def post(self, request, *args, **kwargs):
55 | serializer = self.get_serializer(data=request.data)
56 | if serializer.is_valid():
57 | return Response(serializer.validated_data, status=status.HTTP_200_OK)
58 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
59 |
60 |
61 | class CDMViewSet(viewsets.ModelViewSet):
62 | serializer_class = CDMSerializer
63 |
64 | def get_permissions(self):
65 | if self.action in ['create', 'update', 'partial_update', 'destroy']:
66 | permission_classes = [IsAdmin] # Only admins can create, update, or delete
67 | elif self.action in ['list', 'retrieve']:
68 | permission_classes = [CanViewCDM] # Custom permission for viewing
69 | else:
70 | permission_classes = [permissions.IsAuthenticated] # Default
71 | return [permission() for permission in permission_classes]
72 |
73 | def get_queryset(self):
74 | user = self.request.user
75 | if user.role in ['admin', 'collision_analyst']:
76 | return CDM.objects.all()
77 | elif user.role == 'user':
78 | return CDM.objects.filter(privacy=True)
79 | return CDM.objects.none()
80 |
81 |
82 | # NEW: UserViewSet
83 | class UserViewSet(viewsets.ModelViewSet):
84 | """
85 | Allows admins to view/update all users, and normal users to view/update only themselves.
86 | """
87 | serializer_class = UserSerializer
88 | permission_classes = [permissions.IsAuthenticated]
89 | queryset = User.objects.all()
90 |
91 | def get_queryset(self):
92 | user = self.request.user
93 | if user.is_staff or user.role == 'admin':
94 | return User.objects.all()
95 | # Otherwise, normal user sees only themselves
96 | if self.action == 'list':
97 | # either show themselves in the list, or deny listing
98 | return User.objects.filter(id=user.id)
99 | return User.objects.filter(id=user.id)
100 |
101 | def update(self, request, *args, **kwargs):
102 | # Non-admin user can only update their own record
103 | if not (request.user.is_staff or request.user.role == 'admin'):
104 | if kwargs.get('pk') != str(request.user.id):
105 | return Response({'detail': 'Not allowed to update other users.'}, status=403)
106 | return super().update(request, *args, **kwargs)
107 |
108 | def partial_update(self, request, *args, **kwargs):
109 | # Non-admin user can only partial update their own record
110 | if not (request.user.is_staff or request.user.role == 'admin'):
111 | if kwargs.get('pk') != str(request.user.id):
112 | return Response({'detail': 'Not allowed to update other users.'}, status=403)
113 | return super().partial_update(request, *args, **kwargs)
114 |
115 |
116 | class CurrentUserView(generics.RetrieveAPIView):
117 | serializer_class = UserSerializer
118 | permission_classes = [permissions.IsAuthenticated]
119 |
120 | def get_object(self):
121 | return self.request.user
122 |
123 | class UserNotificationToggleView(APIView):
124 | permission_classes = [permissions.IsAuthenticated]
125 |
126 | def patch(self, request, *args, **kwargs):
127 | """
128 | Enable or disable user notifications.
129 | Expected request data: {"notifications": true/false}
130 | """
131 | new_value = request.data.get("notifications")
132 | if new_value is None:
133 | return Response({"error": "Field 'notifications' must be provided."},
134 | status=status.HTTP_400_BAD_REQUEST)
135 | if not isinstance(new_value, bool):
136 | return Response({"error": "Field 'notifications' must be a boolean."},
137 | status=status.HTTP_400_BAD_REQUEST)
138 | # Update the authenticated user's notifications setting
139 | user = request.user
140 | user.notifications = new_value
141 | user.save()
142 | return Response({"message": f"User notifications updated to {new_value}."},
143 | status=status.HTTP_200_OK)
144 |
--------------------------------------------------------------------------------
/on-orbit-frontend/src/app/user/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import { useRouter } from "next/navigation";
5 | import Link from "next/link";
6 | import Navbar from "../../components/Navbar";
7 |
8 | interface User {
9 | id: string;
10 | email: string;
11 | role: string;
12 | created_at: string;
13 | notifications: boolean;
14 | }
15 |
16 | export default function UserPage() {
17 | const [user, setUser] = useState(null);
18 | const [loading, setLoading] = useState(true);
19 | const [error, setError] = useState(null);
20 | const router = useRouter();
21 |
22 | useEffect(() => {
23 | const fetchUser = async () => {
24 | const token = localStorage.getItem("token");
25 | if (!token) {
26 | setError("Please login to access this page.");
27 | router.push("/login");
28 | return;
29 | }
30 |
31 | try {
32 | const response = await fetch("http://localhost:8000/api/users/current_user/", {
33 | headers: {
34 | "Content-Type": "application/json",
35 | Authorization: `Bearer ${token}`,
36 | },
37 | });
38 |
39 | if (!response.ok) {
40 | if (response.status === 401) {
41 | localStorage.removeItem("token");
42 | router.push("/login");
43 | return;
44 | }
45 | throw new Error("Failed to fetch user info.");
46 | }
47 |
48 | const data = await response.json();
49 | setUser(data);
50 | } catch (err: unknown) {
51 | if (err instanceof Error) {
52 | setError(err.message);
53 | } else {
54 | setError("An unknown error occurred.");
55 | }
56 | } finally {
57 | setLoading(false);
58 | }
59 | };
60 |
61 | fetchUser();
62 | }, [router]);
63 |
64 | const handleToggleNotifications = async () => {
65 | if (!user) return;
66 | const token = localStorage.getItem("token");
67 | if (!token) {
68 | setError("Token not found.");
69 | return;
70 | }
71 | try {
72 | const response = await fetch("http://localhost:8000/api/users/notifications/", {
73 | method: "PATCH",
74 | headers: {
75 | "Content-Type": "application/json",
76 | Authorization: `Bearer ${token}`,
77 | },
78 | body: JSON.stringify({ notifications: !user.notifications }),
79 | });
80 | if (!response.ok) {
81 | throw new Error("Failed to update notifications.");
82 | }
83 | // Update local state with new value
84 | setUser({ ...user, notifications: !user.notifications });
85 | } catch (err: unknown) {
86 | if (err instanceof Error) {
87 | setError(err.message);
88 | } else {
89 | setError("An unknown error occurred.");
90 | }
91 | }
92 | };
93 |
94 | return (
95 |
96 |
97 |
98 | Dashboards /
99 | Overview
100 |
101 |
102 |
103 | {loading ? (
104 |
105 |
106 |
Loading user info...
107 |
108 |
109 |
110 | ) : error ? (
111 |
112 |
113 |
Error
114 |
{error}
115 |
121 |
122 |
123 | ) : user ? (
124 |
125 |
126 |
127 | User Profile
128 |
129 |
130 |
131 | Email: {user.email}
132 |
133 |
134 | Role: {user.role}
135 |
136 |
137 | Created At:{" "}
138 | {new Date(user.created_at).toLocaleString()}
139 |
140 |
141 |
Enable notifications:
142 |
155 |
156 |
157 |
158 |
159 | ) : (
160 |
161 |
No user data available.
162 |
163 | )}
164 |
165 |
166 | );
167 | }
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/get_alfano_test_case.m:
--------------------------------------------------------------------------------
1 | function [tcdata] = get_alfano_test_case(casenum,datapath)
2 |
3 | % Read Excel files with Alfano (2009) test-case data
4 | %
5 | % INPUT:
6 |
7 | % casenum = Case number (1 through 12)
8 | % datapath = Directory for XLS file data (default = pwd)
9 | %
10 | % OUTPUT:
11 | %
12 | % tcdata = Structure with test case data
13 | %
14 | % REFERENCE:
15 | %
16 | % S.Alfano, "Satellite Conjunction Monte Carlo Analysis" AAS 09-233 (2009).
17 | %
18 |
19 | % Set the default path
20 |
21 | if nargin < 2; datapath = []; end
22 | if isempty(datapath); datapath = pwd; end
23 |
24 | % Define the XLS data file names
25 |
26 | casestr = ['Case' num2str(casenum) ' data at '];
27 | file_epoch = fullfile(datapath,[casestr 'Epoch Time.xls']);
28 | file_tca = fullfile(datapath,[casestr 'TCA.xls']);
29 |
30 | % Read the XLS data for EPOCH
31 |
32 | [~,~,raw] = xlsread(file_epoch);
33 | tcdata.casenum = casenum;
34 |
35 | % Define the auxilliary data (not tabulated in the Excel files)
36 |
37 | switch casenum
38 | case 1
39 | tcdata.HBR = 15;
40 | tcdata.PC_MC_10e8 = 0.217467140;
41 | tcdata.desc = 'GEO, nonlinear relative motion';
42 | case 2
43 | tcdata.HBR = 4;
44 | tcdata.PC_MC_10e8 = 0.015736620;
45 | tcdata.desc = 'GEO, nonlinear relative motion';
46 | case 3
47 | tcdata.HBR = 15;
48 | tcdata.PC_MC_10e8 = 0.100846420;
49 | tcdata.desc = 'GEO, linear relative motion';
50 | case 4
51 | tcdata.HBR = 15;
52 | tcdata.PC_MC_10e8 = 0.073089530;
53 | tcdata.desc = 'GEO, nonlinear relative motion';
54 | case 5
55 | tcdata.HBR = 10;
56 | tcdata.PC_MC_10e8 = 0.044498913;
57 | tcdata.desc = 'LEO, linear relative motion';
58 | case 6
59 | tcdata.HBR = 10;
60 | tcdata.PC_MC_10e8 = 0.004300500;
61 | tcdata.desc = 'LEO, near-linear relative motion';
62 | case 7
63 | tcdata.HBR = 10;
64 | tcdata.PC_MC_10e8 = 0.000161462;
65 | tcdata.desc = 'LEO, nonlinear relative motion';
66 | case 8
67 | tcdata.HBR = 4;
68 | tcdata.PC_MC_10e8 = 0.035256080;
69 | tcdata.desc = 'MEO, nonlinear relative motion';
70 | case 9
71 | tcdata.HBR = 6;
72 | tcdata.PC_MC_10e8 = 0.365116060;
73 | tcdata.desc = 'HEO, nonlinear relative motion';
74 | case 10
75 | tcdata.HBR = 6;
76 | tcdata.PC_MC_10e8 = 0.362952470;
77 | tcdata.desc = 'HEO, nonlinear relative motion';
78 | case 11
79 | tcdata.HBR = 4;
80 | tcdata.PC_MC_10e8 = 0.003328530;
81 | tcdata.desc = 'LEO, leader-follower';
82 | case 12
83 | tcdata.HBR = 4;
84 | tcdata.PC_MC_10e8 = 0.002555950;
85 | tcdata.desc = 'LEO, identical orbits';
86 | otherwise
87 | error('Case number out of range (1 through 12)');
88 | end
89 |
90 | % Extract the Excel data
91 |
92 | n = 5;
93 | tcdata.R1e = [raw{n,1}; raw{n,2}; raw{n,3}];
94 |
95 | n = 9;
96 | tcdata.V1e = [raw{n,1}; raw{n,2}; raw{n,3}];
97 |
98 | n=13;
99 | tcdata.P1e = [raw{n ,1} raw{n ,2} raw{n ,3} raw{n ,4} raw{n ,5} raw{n ,6};
100 | raw{n+1,1} raw{n+1,2} raw{n+1,3} raw{n+1,4} raw{n+1,5} raw{n+1,6};
101 | raw{n+2,1} raw{n+2,2} raw{n+2,3} raw{n+2,4} raw{n+2,5} raw{n+2,6};
102 | raw{n+3,1} raw{n+3,2} raw{n+3,3} raw{n+3,4} raw{n+3,5} raw{n+3,6};
103 | raw{n+4,1} raw{n+4,2} raw{n+4,3} raw{n+4,4} raw{n+4,5} raw{n+4,6};
104 | raw{n+5,1} raw{n+5,2} raw{n+5,3} raw{n+5,4} raw{n+5,5} raw{n+5,6}];
105 |
106 | n = 23;
107 | tcdata.R2e = [raw{n,1}; raw{n,2}; raw{n,3}];
108 |
109 | n = 27;
110 | tcdata.V2e = [raw{n,1}; raw{n,2}; raw{n,3}];
111 |
112 | n=31;
113 | tcdata.P2e = [raw{n ,1} raw{n ,2} raw{n ,3} raw{n ,4} raw{n ,5} raw{n ,6};
114 | raw{n+1,1} raw{n+1,2} raw{n+1,3} raw{n+1,4} raw{n+1,5} raw{n+1,6};
115 | raw{n+2,1} raw{n+2,2} raw{n+2,3} raw{n+2,4} raw{n+2,5} raw{n+2,6};
116 | raw{n+3,1} raw{n+3,2} raw{n+3,3} raw{n+3,4} raw{n+3,5} raw{n+3,6};
117 | raw{n+4,1} raw{n+4,2} raw{n+4,3} raw{n+4,4} raw{n+4,5} raw{n+4,6};
118 | raw{n+5,1} raw{n+5,2} raw{n+5,3} raw{n+5,4} raw{n+5,5} raw{n+5,6}];
119 |
120 | % Read the XLS data for TCA
121 |
122 | [~,~,raw] = xlsread(file_tca);
123 |
124 | n = 5;
125 | tcdata.R1o = [raw{n,1}; raw{n,2}; raw{n,3}];
126 |
127 | n = 9;
128 | tcdata.V1o = [raw{n,1}; raw{n,2}; raw{n,3}];
129 |
130 | n=13;
131 | tcdata.P1o = [raw{n ,1} raw{n ,2} raw{n ,3} raw{n ,4} raw{n ,5} raw{n ,6};
132 | raw{n+1,1} raw{n+1,2} raw{n+1,3} raw{n+1,4} raw{n+1,5} raw{n+1,6};
133 | raw{n+2,1} raw{n+2,2} raw{n+2,3} raw{n+2,4} raw{n+2,5} raw{n+2,6};
134 | raw{n+3,1} raw{n+3,2} raw{n+3,3} raw{n+3,4} raw{n+3,5} raw{n+3,6};
135 | raw{n+4,1} raw{n+4,2} raw{n+4,3} raw{n+4,4} raw{n+4,5} raw{n+4,6};
136 | raw{n+5,1} raw{n+5,2} raw{n+5,3} raw{n+5,4} raw{n+5,5} raw{n+5,6}];
137 |
138 | n = 23;
139 | tcdata.R2o = [raw{n,1}; raw{n,2}; raw{n,3}];
140 |
141 | n = 27;
142 | tcdata.V2o = [raw{n,1}; raw{n,2}; raw{n,3}];
143 |
144 | n=31;
145 | tcdata.P2o = [raw{n ,1} raw{n ,2} raw{n ,3} raw{n ,4} raw{n ,5} raw{n ,6};
146 | raw{n+1,1} raw{n+1,2} raw{n+1,3} raw{n+1,4} raw{n+1,5} raw{n+1,6};
147 | raw{n+2,1} raw{n+2,2} raw{n+2,3} raw{n+2,4} raw{n+2,5} raw{n+2,6};
148 | raw{n+3,1} raw{n+3,2} raw{n+3,3} raw{n+3,4} raw{n+3,5} raw{n+3,6};
149 | raw{n+4,1} raw{n+4,2} raw{n+4,3} raw{n+4,4} raw{n+4,5} raw{n+4,6};
150 | raw{n+5,1} raw{n+5,2} raw{n+5,3} raw{n+5,4} raw{n+5,5} raw{n+5,6}];
151 |
152 | % Read the pre-tabulated TCA in seconds after EPOCH
153 |
154 | str = raw{1,1};
155 | [p,~] = string_parts(str);
156 |
157 | if ~strcmpi('epoch)',p{end})
158 | tcdata.tca0_from_epoch = NaN;
159 | else
160 | tcdata.tca0_from_epoch = str2double(p{end-3});
161 |
162 | end
163 |
164 | if isnan(tcdata.tca0_from_epoch)
165 | warning('Unable to read tabulated TCA');
166 | end
167 |
168 | % Calculate the linear estimates for the TCA
169 |
170 | r12 = tcdata.R1e-tcdata.R2e;
171 | v12 = tcdata.V1e-tcdata.V2e;
172 | tcdata.tca_from_epoch_lin = -(r12'*v12)/(v12'*v12);
173 |
174 | r12 = tcdata.R1o-tcdata.R2o;
175 | v12 = tcdata.V1o-tcdata.V2o;
176 | tcdata.tca_from_tca0_lin = -(r12'*v12)/(v12'*v12);
177 |
178 | return
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/conj_bounds_Coppola_UnitTest.m:
--------------------------------------------------------------------------------
1 | classdef (SharedTestFixtures = ...
2 | {matlab.unittest.fixtures.PathFixture('..')}) ...
3 | conj_bounds_Coppola_UnitTest < matlab.unittest.TestCase
4 | % conj_bounds_Coppola_UnitTest - Unit test for conj_bounds_Coppola
5 | %
6 | % =========================================================================
7 | %
8 | % Copyright (c) 2023 United States Government as represented by the
9 | % Administrator of the National Aeronautics and Space Administration.
10 | % All Rights Reserved.
11 | %
12 | % =========================================================================
13 | %
14 | % Initial version: Aug 2023; Latest update: Aug 2023
15 | %
16 | % ----------------- BEGIN CODE -----------------
17 |
18 | properties (Constant)
19 | r1 = [-2622716.15829145 1698990.97254698 7053401.88278209];
20 | v1 = [-4128.50471893557 -5878.79564655151 -118.60127274469];
21 | C1 = [ 30945.8960599633 44172.992380352 1094.127029015 -16.9384961044964 11.1386598710759 45.9406263498005
22 | 44172.992380352 63081.0350093601 1600.54857635509 -24.1640670703954 15.9361771338151 65.5984249372085
23 | 1094.127029015 1600.54857635509 110.587616642047 -0.568332213430976 0.456096601496478 1.65805084742508
24 | -16.9384961044964 -24.1640670703954 -0.568332213430976 0.00928784766563245 -0.00607332603626678 -0.0251311636623764
25 | 11.1386598710759 15.9361771338151 0.456096601496478 -0.00607332603626678 0.00406599298830971 0.0165654694968024
26 | 45.9406263498005 65.5984249372085 1.65805084742508 -0.0251311636623764 0.0165654694968024 0.0682232639995689];
27 | r2 = [-2631783.12714667 1685565.41148418 7053328.56776558];
28 | v2 = [-4112.58245687929 -5889.50355090998 -126.881531658755];
29 | C2 = [ 37176528.2096518 51779295.1953059 1237287.58668701 -20025.8397475978 12497.8621510473 54618.847730921
30 | 51779295.195306 72120313.4426337 1723250.32582261 -27893.1756367764 17408.3723881467 76074.0995332777
31 | 1237287.58668701 1723250.32582261 41984.4552410924 -666.613016373449 416.862049452506 1817.7275694543
32 | -20025.8397475978 -27893.1756367764 -666.613016373449 10.7880803834256 -6.73318789560721 -29.422096492432
33 | 12497.8621510473 17408.3723881467 416.862049452506 -6.73318789560721 4.20345007734003 18.3622071009157
34 | 54618.847730921 76074.0995332777 1817.7275694543 -29.422096492432 18.3622071009157 80.2453907683188];
35 | end
36 |
37 | properties (TestParameter)
38 | defaultR = {(conj_bounds_Coppola_UnitTest.r2 - conj_bounds_Coppola_UnitTest.r1)'};
39 | defaultV = {(conj_bounds_Coppola_UnitTest.v2 - conj_bounds_Coppola_UnitTest.v1)'};
40 | defaultC = {conj_bounds_Coppola_UnitTest.C1 + conj_bounds_Coppola_UnitTest.C2};
41 | defaultHBR = {14.8};
42 | defaultGamma = {1e-16};
43 | end
44 |
45 | methods (Test)
46 | % Tests that output is the same without an argument and with its
47 | % default value
48 | function testDefaultParameter (testCase, defaultGamma, defaultHBR, defaultR, defaultV, defaultC)
49 | [a_t0, a_t1, a_t0g, a_t1g] = conj_bounds_Coppola(defaultGamma, defaultHBR, defaultR, defaultV, defaultC);
50 | [e_t0, e_t1, e_t0g, e_t1g] = conj_bounds_Coppola(defaultGamma, defaultHBR, defaultR, defaultV, defaultC, false);
51 |
52 | act = [a_t0, a_t1, a_t0g, a_t1g];
53 | exp = [e_t0, e_t1, e_t0g, e_t1g];
54 |
55 | testCase.verifyEqual(act, exp);
56 | end
57 |
58 | % Tests that passing vector inputs returns outputs of the correct
59 | % dimensions
60 | function testVectorizedOutputDimensions (testCase, defaultGamma, defaultHBR, defaultR, defaultV, defaultC)
61 | gamma = repmat(defaultGamma, 3, 2);
62 | [t0, t1, t0g, t1g] = conj_bounds_Coppola(gamma, defaultHBR, defaultR, defaultV, defaultC);
63 |
64 | testCase.verifyEqual(size(t0), size(gamma));
65 | testCase.verifyEqual(size(t1), size(gamma));
66 | testCase.verifyEqual(size(t0g), [1 1]);
67 | testCase.verifyEqual(size(t1g), [1 1]);
68 | end
69 |
70 | % Tests that both 3x3 and 6x6 covariance matrices can be used
71 | % without error (note that since these Pc calculations assume no
72 | % velocity covariance, the cases will be equal)
73 | function testCovarianceSizes (testCase, defaultGamma, defaultHBR, defaultR, defaultV, defaultC)
74 | C3 = defaultC(1:3, 1:3);
75 | [t03, t13, t0g3, t1g3] = conj_bounds_Coppola(defaultGamma, defaultHBR, defaultR, defaultV, C3);
76 | [t06, t16, t0g6, t1g6] = conj_bounds_Coppola(defaultGamma, defaultHBR, defaultR, defaultV, defaultC);
77 |
78 | act = [t03, t13, t0g3, t1g3];
79 | exp = [t06, t16, t0g6, t1g6];
80 |
81 | testCase.verifyEqual(act, exp);
82 | end
83 |
84 | % Tests bounds for zero relative velocity
85 | function testZeroRelativeVelocity (testCase, defaultGamma, defaultHBR, defaultR, defaultC)
86 | [t0, t1, t0g, t1g] = conj_bounds_Coppola(defaultGamma, defaultHBR, defaultR, [0, 0, 0], defaultC);
87 | act = [t0, t1, t0g, t1g];
88 | exp = [-Inf, Inf, -Inf, Inf];
89 |
90 | testCase.verifyEqual(act, exp);
91 | end
92 |
93 | end
94 |
95 | end
96 |
97 | % ----------------- END OF CODE -----------------
98 | %
99 | % Please record any changes to the software in the change history
100 | % shown below:
101 | %
102 | %---------------- CHANGE HISTORY ------------------
103 | % Developer | Date | Description
104 | %--------------------------------------------------
105 | % E. White | 08-07-2023 | Initial development
106 |
107 | % =========================================================================
108 | %
109 | % Copyright (c) 2023 United States Government as represented by the
110 | % Administrator of the National Aeronautics and Space Administration.
111 | % All Rights Reserved.
112 | %
113 | % =========================================================================
114 |
--------------------------------------------------------------------------------
/on-orbit-frontend/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | "use client";
3 |
4 | import Link from 'next/link';
5 | import Image from 'next/image';
6 | import { useEffect, useState } from 'react';
7 | import { usePathname, useRouter } from 'next/navigation';
8 | import { worksans } from '@/app/styles/font';
9 | import { ChartPie, Satellite, User } from 'lucide-react';
10 |
11 | export default function Navbar() {
12 | const [isLoggedIn, setIsLoggedIn] = useState(false);
13 | const [username, setUsername] = useState('');
14 | const router = useRouter();
15 | const pathname = usePathname();
16 |
17 | useEffect(() => {
18 | const token = localStorage.getItem('token');
19 | const storedName = localStorage.getItem('username');
20 | if (token) {
21 | setIsLoggedIn(true);
22 | if (storedName) setUsername(storedName);
23 | }
24 | }, []);
25 |
26 | const handleLogout = () => {
27 | localStorage.removeItem('token');
28 | localStorage.removeItem('username');
29 | setIsLoggedIn(false);
30 | router.push('/');
31 | };
32 |
33 | const isDashboardOrCesium = pathname === "/dashboard" || pathname && pathname.startsWith("/cesium-view") || pathname && pathname.startsWith("/maneuvering") || pathname && pathname.startsWith("/user");
34 |
35 | return (
36 |
37 | {/* Top navbar for other pages */}
38 | {!isDashboardOrCesium && (
39 |
69 | )}
70 |
71 | {/* Sidebar navbar for dashboard + cesium-view */}
72 | {isDashboardOrCesium && (
73 |
74 |
125 |
126 | )}
127 |
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/Orbit_Predictor-BackEnd/api/matlab/Utils/CovRemEigValClip2x2.m:
--------------------------------------------------------------------------------
1 | function [ClipStatus,Arem] = CovRemEigValClip2x2(Araw,Lclip)
2 | % =========================================================================
3 | %
4 | % This function is a vectorized form of CovRemEigValClip() which is
5 | % designed to detect a non-positive definite (NPD) covariance matrix, and
6 | % if necessary, perform adjustments to make it either positive
7 | % semi-definite (PSD) or positive definite (PD). This vectorized form of
8 | % the function assumes the set of covariances passed in, "Araw", each
9 | % represent the components of a 2x2 covariance matrix.
10 | %
11 | % This function uses the "eigenvalue clipping" method (Hall et al, 2017).
12 | % It first checks if the input covariance "Araw" has any eigenvalues "Lraw"
13 | % less than the specified minimum clipping limit "Lclip". If so, the
14 | % function remediates these eigenvalues by setting them to the clipping
15 | % limit, and then generates the remediated covariance by using the
16 | % eigenvectors of the original unremediated covariance "v1" and "v2". If
17 | % Araw has a minimum eigenvalue greater than or equal to the specified
18 | % clipping limit, "l1" and "l2" >= Lclip, then no remediation is required
19 | % or performed, but the function still produces all of the quantities
20 | % requested by the user in the output argument list.
21 | %
22 | % For reference (see Hall et al, 2017):
23 | % PD covariances have eigenvalues that are all positive, or l1 and l2 > 0.
24 | % PSD covariances have one or more zero eigenvalues, or l1 or l2 = 0.
25 | % NPD covariances have one or more negative eigenvalues, or l1 or l2 < 0.
26 | %
27 | % A zero eigenvalue clipping limit, Lclip = 0, will change an NPD input
28 | % covariance into a PSD covariance, to within the estimation accuracy of
29 | % Matlab's "eig" function.
30 | %
31 | % A positive eigenvalue clipping limit, Lclip > 0, will change the input
32 | % covariance into a PD covariance, to within the estimation accuracy of
33 | % Matlab's "eig" function.
34 | %
35 | % The function outputs flags indicating if any eigenvalue clipping
36 | % occurred and the remediated covariances. If no remediation was needed,
37 | % the original "Araw" row value will be returned. Note that this is a
38 | % reduced list of outputs from CovRemEigValClip() method.
39 | %
40 | % =========================================================================
41 | %
42 | % REQUIRED INPUT:
43 | %
44 | % Araw = Input raw or unremediated 2x2 covariance matrices [nx3].
45 | % - Araw is assumed represent a set of n real 2x2
46 | % diagonally symmetric covariance matrices.
47 | % Araw(:,1) is the 1,1 component for each symmetric
48 | % matrix
49 | % Araw(:,2) is the 1,2 and 2,1 component for each
50 | % symmetric matrix
51 | % Araw(:,3) is the 2,2 component for each symmetric
52 | % matrix
53 | %
54 | % OPTIONAL INPUT:
55 | %
56 | % Lclip = Clipping limit for the eigenvalues [1x1].
57 | % - If Lclip is not input at all, or is input empty,
58 | % then it is set to a default value of zero.
59 | % - If Lclip is input with a value of less than zero,
60 | % then the function produces an error.
61 | % - Recommendations for Pc-related calculations, see
62 | % Hall et al (2017):
63 | % 1) Use Lclip = 0 for remediation when sampling states
64 | % to be used in Monte Carlo Pc calculations.
65 | % 2) Use Lclip = (1e-4*HBR)^2 for remediation of
66 | % 2x2 marginalized position covariances, when
67 | % calculating Mahalanobis distances, 3DPc, or 2DPc
68 | % values.
69 | %
70 | % =========================================================================
71 | %
72 | % OUTPUT:
73 | %
74 | % ClipStatus = Clipping status of Araw = (min(Lraw) < Lclip) [nx1].
75 | % false => No eigenvalue clipping required
76 | % true => Eigenvalue clipping performed
77 | %
78 | % Arem = Remediated covariance matrices [nx3].
79 | %
80 | % =========================================================================
81 | %
82 | % REFERENCE:
83 | %
84 | % D.T.Hall, M.D.Hejduk, and L.C.Johnson, 2017, AAS-17-567, "Remediating
85 | % Non-Positive Definite State Covariances for Collision Probability
86 | % Estimation"
87 | %
88 | % =========================================================================
89 |
90 | % Initializations, defaults, and input error checks
91 |
92 | Nargin = nargin;
93 |
94 | if (Nargin < 2); Lclip = []; end
95 |
96 | if isempty(Lclip)
97 | Lclip = 0;
98 | elseif ~isreal(Lclip)
99 | error('Lclip must be real');
100 | elseif (Lclip < 0)
101 | error('Lclip cannot be negative');
102 | end
103 |
104 |
105 | % =========================================================================
106 | %
107 | % Begin calculating the required output
108 | %
109 | % =========================================================================
110 |
111 | % Ensure that the covariance is a 2D nx3 matrix
112 |
113 | szC = size(Araw);
114 |
115 | if (numel(szC) ~= 2)
116 | error('Array needs to be a 2D matrix');
117 | end
118 |
119 | if (szC(2) ~= 3)
120 | error('Matrix needs to be an nx3 matrix to represent 2x2 symmetric covariances');
121 | end
122 |
123 | % Ensure that the covariance has all real elements
124 |
125 | if ~isreal(Araw)
126 | error('Covariance matrix cannot have imaginary elements');
127 | end
128 |
129 | % Calculate the eigen-decomposition of Araw
130 |
131 | [v1,v2,l1,l2] = eig2x2(Araw);
132 | % [Vraw,Draw] = eig(Araw);
133 | Lraw = [l1 l2];
134 |
135 | % Ensure that the eigenvalues and eigenvectors have real values
136 |
137 | if ~isreal(v1) || ~isreal(v2) || ~isreal(l1) || ~isreal(l2)
138 | error('Eigenvalues and eigenvectors must be real');
139 | end
140 |
141 | % Clip the eigenvalues if required, and define the clipping status
142 |
143 | Lrem = Lraw;
144 | ClipStatus = min(Lraw,[],2) < Lclip;
145 | Lrem(Lraw(:,1) < Lclip, 1) = Lclip(Lraw(:,1) < Lclip);
146 | Lrem(Lraw(:,2) < Lclip, 2) = Lclip(Lraw(:,2) < Lclip);
147 |
148 | % Remediated covariance
149 | Arem = Araw;
150 | idx = ClipStatus;
151 | Arem(idx,:) = [v1(idx,1).^2.*Lrem(idx,1)+v2(idx,1).^2.*Lrem(idx,2) ...
152 | v1(idx,1).*v1(idx,2).*Lrem(idx,1)+v2(idx,1).*v2(idx,2).*Lrem(idx,2) ...
153 | v1(idx,2).^2.*Lrem(idx,1)+v2(idx,2).^2.*Lrem(idx,2)];
154 | end
155 |
156 | % ----------------- END OF CODE ------------------
157 | %
158 | % Please record any changes to the software in the change history
159 | % shown below:
160 | %
161 | % ----------------- CHANGE HISTORY ------------------
162 | % Developer | Date | Description
163 | % ---------------------------------------------------
164 | % L. Baars | 03/11/2020 | Initial Development using
165 | % CovRemEigValClip.m as a starting point.
166 | % L. Baars | 04/29/2022 | Moved eig2x2 into its own function in the
167 | % utils subdirectory.
--------------------------------------------------------------------------------
/on-orbit-frontend/src/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useState } from 'react';
4 | import { useRouter } from 'next/navigation';
5 | import Footer from '@/components/footer/page';
6 |
7 | export default function SignUp() {
8 | const [email, setEmail] = useState('');
9 | const [password, setPassword] = useState('');
10 | const [confirmPassword, setConfirmPassword] = useState('');
11 | const [isAdmin, setIsAdmin] = useState(false);
12 | const [registrationCode, setRegistrationCode] = useState('');
13 | const [error, setError] = useState('');
14 | const router = useRouter();
15 |
16 | const handleSignUp = async (e: React.FormEvent) => {
17 | e.preventDefault();
18 | setError('');
19 |
20 | if (password !== confirmPassword) {
21 | alert("Passwords do not match!");
22 | return;
23 | }
24 |
25 | // If the user is registering as admin, ensure a registration code is provided.
26 | if (isAdmin && registrationCode.trim() === '') {
27 | alert("Registration code is required for admin registration.");
28 | return;
29 | }
30 |
31 | try {
32 | const response = await fetch("http://localhost:8000/api/register/", {
33 | method: "POST",
34 | headers: {
35 | "Content-Type": "application/json",
36 | },
37 | body: JSON.stringify({
38 | email,
39 | password,
40 | // Only include the registration code if admin is selected.
41 | registration_code: isAdmin ? registrationCode : undefined,
42 | is_admin: isAdmin
43 | }),
44 | });
45 |
46 | if (!response.ok) {
47 | const errorData = await response.json();
48 | setError(errorData.email || errorData.message || "Something went wrong!");
49 | return;
50 | }
51 |
52 | alert("Sign up successful! Please log in to continue.");
53 | router.push('/login');
54 | } catch (err) {
55 | console.error("Signup error:", err);
56 | alert("An error occurred while signing up. Please try again.");
57 | }
58 | };
59 |
60 | return (
61 |
62 |
63 |
Sign Up
64 | {error &&
{error}
}
65 |
145 |
146 |
147 |
148 | );
149 | }
150 |
--------------------------------------------------------------------------------