├── .gitignore ├── Orbit_Predictor-BackEnd ├── .DS_Store ├── .env.example ├── api │ ├── .DS_Store │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── authentication.py │ ├── management │ │ └── commands │ │ │ └── seed_cdm_data.py │ ├── matlab │ │ ├── .DS_Store │ │ ├── Pc2D_Foster.m │ │ ├── Pc3D_Hall.m │ │ ├── PcCircleWithConjData.m │ │ └── Utils │ │ │ ├── CheckAndResizeCov.m │ │ │ ├── CheckAndResizePosVel.m │ │ │ ├── CovRemEigValClip.m │ │ │ ├── CovRemEigValClip2x2.m │ │ │ ├── EquinoctialMatrices.m │ │ │ ├── FindNearbyCA.m │ │ │ ├── GetAlfanoTestCases.m │ │ │ ├── LinearConjDuration.m │ │ │ ├── PeakOverlapMD2.m │ │ │ ├── PeakOverlapPos.m │ │ │ ├── Product3x3.m │ │ │ ├── RemediateCovariance2x2.m │ │ │ ├── RetrogradeReorientation.m │ │ │ ├── UnitTests │ │ │ ├── CheckAndResizeCov_UnitTest.m │ │ │ ├── FindNearbyCA_UnitTest.m │ │ │ ├── InputFiles │ │ │ │ └── FindNearbyCA_TestCases.mat │ │ │ ├── LinearConjDuration_UnitTest.m │ │ │ ├── Product3x3_UnitTest.m │ │ │ ├── conj_bounds_Coppola_UnitTest.m │ │ │ └── eig2x2_UnitTest.m │ │ │ ├── conj_bounds_Coppola.m │ │ │ ├── eig2x2.m │ │ │ ├── getLebedevSphere.m │ │ │ ├── get_alfano_test_case.m │ │ │ ├── get_covXcorr_parameters.m │ │ │ ├── jacobian_E0_to_Xt.m │ │ │ └── set_default_param.m │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_user.py │ │ ├── 0003_remove_collision_conjunction_id_and_more.py │ │ ├── 0004_remove_user_password_hash_cdm_privacy_user_groups_and_more.py │ │ ├── 0004_rename_risk_factor_collision_probability_of_collision_and_more.py │ │ ├── 0005_merge_20250203_0240.py │ │ ├── 0006_cdm_sat1_catalog_name_cdm_sat1_covariance_method_and_more.py │ │ ├── 0007_organization.py │ │ ├── 0008_alter_organization_alert_threshold_and_more.py │ │ ├── 0009_cdm_organizations.py │ │ ├── 0010_remove_cdm_organizations_organization_cdms.py │ │ ├── 0011_user_interested_cdms_and_more.py │ │ ├── 0012_user_notifications.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── cdm.py │ │ ├── collision.py │ │ ├── organization.py │ │ ├── probability_calc.py │ │ └── user.py │ ├── permissions.py │ ├── sample_data │ │ └── oct5_data │ │ │ ├── cdm0.json │ │ │ ├── cdm1.json │ │ │ ├── cdm2.json │ │ │ ├── cdm3.json │ │ │ └── cdm4.json │ ├── schemas.py │ ├── serializers │ │ ├── .DS_Store │ │ ├── __init__.py │ │ ├── cdm_serializer.py │ │ ├── collision_serializer.py │ │ ├── organization_serializer.py │ │ ├── probability_calc_serializer.py │ │ └── user_serializer.py │ ├── tests │ │ ├── __init__.py │ │ └── tests.py │ ├── urls.py │ └── views │ │ ├── __init__.py │ │ ├── cdm_views.py │ │ ├── collision_views.py │ │ ├── organization_views.py │ │ ├── probability_calc_views.py │ │ ├── refresh_token_views.py │ │ ├── tradespace_heatmap_views.py │ │ ├── tradespace_linear_views.py │ │ └── user_views.py ├── db.sqlite3 ├── manage.py ├── orbit_predictor │ ├── .DS_Store │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── test.py ├── README.md ├── on-orbit-frontend ├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── csa.png │ ├── logo.png │ ├── mcgill.png │ └── world-110m.json ├── src │ ├── app │ │ ├── (root) │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── about │ │ │ └── page.tsx │ │ ├── cdm-detail │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── cesium-view │ │ │ ├── [id] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── login │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── maneuvering │ │ │ ├── heatmap │ │ │ │ └── [id] │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ └── linear │ │ │ │ └── [id] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── signup │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── styles │ │ │ └── font.ts │ │ └── user │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── components │ │ ├── Navbar.tsx │ │ ├── about │ │ │ └── about.tsx │ │ ├── csa │ │ │ └── csa.tsx │ │ ├── footer │ │ │ └── page.tsx │ │ ├── landing │ │ │ └── landing.tsx │ │ └── ui │ │ │ └── input.tsx │ └── lib │ │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── requirements.txt └── settings.json /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | Orbit_Predictor-BackEnd/.env 3 | Orbit_Predictor-BackEnd/api/__pycache__/ 4 | Orbit_Predictor-BackEnd/orbit_predictor/__pycache__/ 5 | Orbit_Predictor-BackEnd/api/migrations/__pycache__/ 6 | Orbit_Predictor-BackEnd/api/models/__pycache__/ 7 | Orbit_Predictor-BackEnd/api/serializers/__pycache__/ 8 | Orbit_Predictor-BackEnd/api/views/__pycache__/ 9 | Orbit_Predictor-BackEnd/api/management/commands/__pycache__/ 10 | node_modules 11 | 12 | .env -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/.DS_Store -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/.env.example: -------------------------------------------------------------------------------- 1 | SUPABASE_DB_NAME= 2 | SUPABASE_DB_USER= 3 | SUPABASE_DB_PASSWORD= 4 | SUPABASE_DB_HOST= 5 | SUPABASE_DB_PORT= 6 | SECRET_KEY= 7 | DEBUG= 8 | ALLOWED_HOSTS= 9 | JWT_SECRET_KEY= 10 | ADMIN_REGISTRATION_CODE= 11 | 12 | # Generate these from your email provider (e.g. Gmail with your email and an app password) 13 | EMAIL_HOST_USER= 14 | EMAIL_HOST_PASSWORD= 15 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/.DS_Store -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/__init__.py -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Conjunction, Collision, ProbabilityCalc, CDM 3 | 4 | admin.site.register(Conjunction) 5 | admin.site.register(Collision) 6 | admin.site.register(ProbabilityCalc) 7 | admin.site.register(CDM) 8 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/matlab/.DS_Store -------------------------------------------------------------------------------- /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/CheckAndResizePosVel.m: -------------------------------------------------------------------------------- 1 | % checks the sizes of the matrices and resizes v if needed 2 | function [numR, v] = CheckAndResizePosVel(r, v) 3 | [numR, rColumns] = size(r); 4 | if rColumns ~= 3 5 | error('r matrix must have 3 columns!'); 6 | end 7 | [numV, vColumns] = size(v); 8 | if vColumns ~= 3 9 | error('v matrix must have 3 columns!'); 10 | end 11 | if numV ~= numR 12 | if numV == 1 13 | v = repmat(v,numR,1); 14 | else 15 | error('v matrix cannot be resized to match r matrix'); 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /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/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/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/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/matlab/Utils/UnitTests/InputFiles/FindNearbyCA_TestCases.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/matlab/Utils/UnitTests/InputFiles/FindNearbyCA_TestCases.mat -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/matlab/Utils/set_default_param.m: -------------------------------------------------------------------------------- 1 | function params = set_default_param(params,param_name,default_param_value) 2 | 3 | % Set a default value in a params structure 4 | 5 | if isempty(param_name) || ~ischar(param_name) 6 | error('Invalid input param_name'); 7 | end 8 | 9 | if ~isfield(params,param_name) 10 | % If param doesn't exist as a field, then set it to the default value 11 | % params = setfield(params,param_name,default_param_value); 12 | params.(param_name) = default_param_value; 13 | elseif isempty(params.(param_name)) && ~isempty(default_param_value) 14 | % If param is empty, then set it to the default value 15 | % params = setfield(params,param_name,default_param_value); 16 | params.(param_name) = default_param_value; 17 | end 18 | 19 | return 20 | end 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0002_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2024-12-05 22:17 2 | 3 | import uuid 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='User', 16 | fields=[ 17 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 18 | ('email', models.EmailField(max_length=254, unique=True)), 19 | ('password_hash', models.CharField(max_length=128)), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ], 22 | ), 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 | -------------------------------------------------------------------------------- /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/migrations/0004_rename_risk_factor_collision_probability_of_collision_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2024-12-07 16:52 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0003_remove_collision_conjunction_id_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='collision', 15 | old_name='risk_factor', 16 | new_name='probability_of_collision', 17 | ), 18 | migrations.RemoveField( 19 | model_name='collision', 20 | name='collision_date', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0005_merge_20250203_0240.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-03 02:40 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0004_remove_user_password_hash_cdm_privacy_user_groups_and_more'), 10 | ('api', '0004_rename_risk_factor_collision_probability_of_collision_and_more'), 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /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/migrations/0007_organization.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-15 23:48 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0006_cdm_sat1_catalog_name_cdm_sat1_covariance_method_and_more'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Organization', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=255, unique=True)), 19 | ('alert_threshold', models.FloatField(default=1e-09)), 20 | ('users', models.ManyToManyField(related_name='organizations', to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0008_alter_organization_alert_threshold_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-16 00:16 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0007_organization'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='organization', 16 | name='alert_threshold', 17 | field=models.FloatField(default=5.36104950954095e-09), 18 | ), 19 | migrations.AlterField( 20 | model_name='organization', 21 | name='users', 22 | field=models.ManyToManyField(blank=True, related_name='organizations', to=settings.AUTH_USER_MODEL), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0009_cdm_organizations.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-16 00:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0008_alter_organization_alert_threshold_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='cdm', 15 | name='organizations', 16 | field=models.ManyToManyField(blank=True, related_name='cdms', to='api.organization'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0010_remove_cdm_organizations_organization_cdms.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-16 00:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0009_cdm_organizations'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='cdm', 15 | name='organizations', 16 | ), 17 | migrations.AddField( 18 | model_name='organization', 19 | name='cdms', 20 | field=models.ManyToManyField(blank=True, related_name='organizations', to='api.cdm'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0011_user_interested_cdms_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-02-16 01:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0010_remove_cdm_organizations_organization_cdms'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='interested_cdms', 16 | field=models.ManyToManyField(blank=True, related_name='interested_users', to='api.cdm'), 17 | ), 18 | migrations.AlterField( 19 | model_name='organization', 20 | name='alert_threshold', 21 | field=models.FloatField(default=1e-09), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/0012_user_notifications.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-04-07 03:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0011_user_interested_cdms_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='notifications', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/migrations/__init__.py -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .collision import Collision 2 | from .probability_calc import ProbabilityCalc 3 | from .cdm import CDM 4 | from .user import User 5 | from .organization import Organization 6 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/models/probability_calc.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .cdm import CDM 3 | 4 | class ProbabilityCalc(models.Model): 5 | cdm = models.ForeignKey(CDM, on_delete=models.CASCADE, related_name='probability_calcs') 6 | probability_value = models.FloatField() 7 | time_to_impact = models.DurationField() 8 | 9 | def __str__(self): 10 | return f"ProbabilityCalc {self.id} - CDM {self.cdm.id}" -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | ] -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/schemas.py: -------------------------------------------------------------------------------- 1 | from rest_framework.schemas.openapi import SchemaGenerator 2 | 3 | class CustomSchemaGenerator(SchemaGenerator): 4 | def get_schema(self, request=None, public=False): 5 | return super().get_schema(request=request, public=public) -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/serializers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/serializers/.DS_Store -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .collision_serializer import CollisionSerializer 2 | from .probability_calc_serializer import ProbabilityCalcSerializer 3 | from .cdm_serializer import CDMSerializer 4 | from .user_serializer import UserSerializer, LoginSerializer, CDMSerializer, RefreshTokenSerializer 5 | from .organization_serializer import OrganizationSerializer 6 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/serializers/cdm_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from ..models import CDM 3 | 4 | class CDMSerializer(serializers.ModelSerializer): 5 | class Meta: 6 | model = CDM 7 | fields = '__all__' -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/serializers/collision_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from ..models import Collision 3 | 4 | class CollisionSerializer(serializers.ModelSerializer): 5 | class Meta: 6 | model = Collision 7 | fields = '__all__' -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/serializers/probability_calc_serializer.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from ..models import ProbabilityCalc 3 | 4 | class ProbabilityCalcSerializer(serializers.ModelSerializer): 5 | class Meta: 6 | model = ProbabilityCalc 7 | fields = '__all__' -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/api/tests/__init__.py -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/tests/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /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/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .collision_views import CollisionListCreateView, CollisionDetailView 2 | from .probability_calc_views import ProbabilityCalcListCreateView, ProbabilityCalcDetailView 3 | from .cdm_views import CDMSerializerListCreateView, CDMCalcDetailView, CDMViewSet, CDMCreateView, CDMPrivacyToggleView 4 | from .user_views import RegisterView, LoginView, UserViewSet, CurrentUserView, UserNotificationToggleView 5 | from .refresh_token_views import RefreshTokenView 6 | from .organization_views import OrganizationViewSet 7 | from .tradespace_heatmap_views import CollisionTradespaceView 8 | from .tradespace_linear_views import CollisionLinearTradespaceView 9 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/views/collision_views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics 2 | from ..models import Collision 3 | from ..serializers import CollisionSerializer 4 | 5 | class CollisionListCreateView(generics.ListCreateAPIView): 6 | queryset = Collision.objects.all() 7 | serializer_class = CollisionSerializer 8 | 9 | class CollisionDetailView(generics.RetrieveUpdateDestroyAPIView): 10 | queryset = Collision.objects.all() 11 | serializer_class = CollisionSerializer -------------------------------------------------------------------------------- /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/views/probability_calc_views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics 2 | from ..models import ProbabilityCalc 3 | from ..serializers import ProbabilityCalcSerializer 4 | 5 | class ProbabilityCalcListCreateView(generics.ListCreateAPIView): 6 | queryset = ProbabilityCalc.objects.all() 7 | serializer_class = ProbabilityCalcSerializer 8 | 9 | class ProbabilityCalcDetailView(generics.RetrieveUpdateDestroyAPIView): 10 | queryset = ProbabilityCalc.objects.all() 11 | serializer_class = ProbabilityCalcSerializer -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/api/views/refresh_token_views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, status 2 | from rest_framework.response import Response 3 | from rest_framework.permissions import AllowAny 4 | from ..serializers import RefreshTokenSerializer 5 | 6 | class RefreshTokenView(generics.GenericAPIView): 7 | serializer_class = RefreshTokenSerializer 8 | permission_classes = [AllowAny] 9 | 10 | def post(self, request, *args, **kwargs): 11 | serializer = self.get_serializer(data=request.data) 12 | if serializer.is_valid(): 13 | return Response(serializer.validated_data, status=status.HTTP_200_OK) 14 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/db.sqlite3 -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'orbit_predictor.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/orbit_predictor/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/orbit_predictor/.DS_Store -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/orbit_predictor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/Orbit_Predictor-BackEnd/orbit_predictor/__init__.py -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/orbit_predictor/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for orbit_predictor project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'orbit_predictor.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /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/orbit_predictor/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for orbit_predictor project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.1/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import include, path 19 | 20 | urlpatterns = [ 21 | # path('admin/', admin.site.urls), 22 | path('api/', include('api.urls')), 23 | ] 24 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/orbit_predictor/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for orbit_predictor project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'orbit_predictor.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Orbit_Predictor-BackEnd/test.py: -------------------------------------------------------------------------------- 1 | import matlab.engine 2 | import argparse 3 | 4 | def main(): 5 | parser = argparse.ArgumentParser(description="Run MATLAB sqrt function from Python") 6 | parser.add_argument("number", type=float, help="The number to calculate the square root of") 7 | args = parser.parse_args() 8 | 9 | eng = matlab.engine.start_matlab() 10 | result = eng.sqrt(args.number) 11 | print(result) 12 | eng.quit() 13 | 14 | if __name__ == "__main__": 15 | main() -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /on-orbit-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /on-orbit-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /on-orbit-frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /on-orbit-frontend/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /on-orbit-frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /on-orbit-frontend/public/csa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/on-orbit-frontend/public/csa.png -------------------------------------------------------------------------------- /on-orbit-frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/on-orbit-frontend/public/logo.png -------------------------------------------------------------------------------- /on-orbit-frontend/public/mcgill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/on-orbit-frontend/public/mcgill.png -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/(root)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/footer/page"; 2 | import Navbar from "../../components/Navbar"; 3 | import { worksans } from "../styles/font"; 4 | import Csa from "@/components/csa/csa"; 5 | 6 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 7 | return( 8 |
9 | 10 | {children} 11 | 12 |
13 |
14 | ) 15 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/(root)/page.tsx: -------------------------------------------------------------------------------- 1 | import Csa from "@/components/csa/csa"; 2 | import Landing from "@/components/landing/landing"; 3 | // import Image from "next/image"; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | {/* */} 10 |
11 | ); 12 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/Navbar"; 2 | import Footer from "@/components/footer/page"; 3 | import About from "@/components/about/about"; 4 | import Csa from "@/components/csa/csa"; 5 | 6 | export default function AboutPage() { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 |
14 | ); 15 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/cesium-view/[id]/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../../../components/Navbar"; 2 | import { worksans } from "../../styles/font"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/cesium-view/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../../components/Navbar"; 2 | import { worksans } from "../styles/font"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../../components/Navbar"; 2 | import { worksans } from "../styles/font"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Erik-Cupsa/On-Orbit-Collision-Predictor/a27e0cf4883c46c2dc3780952c87eacaa0f4655c/on-orbit-frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { worksans } from "./styles/font"; 4 | 5 | export const metadata: Metadata = { 6 | title: "On Orbit Collision Predictor", 7 | }; 8 | 9 | export default function RootLayout({ 10 | children, 11 | }: Readonly<{ 12 | children: React.ReactNode; 13 | }>) { 14 | return ( 15 | 16 | 17 | {children} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/loading.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/Navbar" 2 | 3 | export default function Loading() { 4 | return ( 5 | <> 6 | 7 |
8 | {/* */} 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | ); 20 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../../components/Navbar"; 2 | import { worksans } from "../styles/font"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /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 |
58 |
59 | 62 | setEmail(e.target.value)} 67 | className="shadow appearance-none bg-white border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 68 | required 69 | /> 70 |
71 |
72 | 75 | setPassword(e.target.value)} 80 | className="shadow appearance-none border bg-white rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 81 | required 82 | /> 83 |
84 |
85 | 91 |
92 |
93 |
94 | {/* */} 95 |
96 |
97 | ); 98 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/maneuvering/heatmap/[id]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { worksans } from "@/app/styles/font"; 2 | import Navbar from "@/components/Navbar"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/maneuvering/linear/[id]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { worksans } from "@/app/styles/font"; 2 | import Navbar from "@/components/Navbar"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/signup/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "../../components/Navbar"; 2 | import { worksans } from "../styles/font"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /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 |
66 |
67 | 70 | setEmail(e.target.value)} 75 | className="shadow appearance-none bg-white border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 76 | required 77 | /> 78 |
79 | 80 |
81 | 91 |
92 | 93 | {/* Show registration code field only if admin checkbox is checked */} 94 | {isAdmin && ( 95 |
96 | 99 | setRegistrationCode(e.target.value)} 104 | className="shadow appearance-none bg-white border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 105 | required 106 | /> 107 |
108 | )} 109 | 110 |
111 | 114 | setPassword(e.target.value)} 119 | className="shadow appearance-none bg-white border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 120 | required 121 | /> 122 |
123 |
124 | 127 | setConfirmPassword(e.target.value)} 132 | className="shadow appearance-none bg-white border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 133 | required 134 | /> 135 |
136 |
137 | 143 |
144 |
145 |
146 |
147 |
148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/styles/font.ts: -------------------------------------------------------------------------------- 1 | import { Montserrat, Merriweather, Work_Sans } from "next/font/google"; 2 | 3 | export const worksans = Work_Sans({ 4 | subsets: ["latin"], 5 | weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], 6 | display: "swap", 7 | }); 8 | 9 | export const montserrat = Montserrat({ 10 | subsets: ["latin"], 11 | weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], 12 | display: "swap", 13 | }); 14 | 15 | export const merriweather = Merriweather({ 16 | subsets: ["latin"], 17 | weight: ["300", "400", "700", "900"], 18 | display: "swap", 19 | }); 20 | -------------------------------------------------------------------------------- /on-orbit-frontend/src/app/user/layout.tsx: -------------------------------------------------------------------------------- 1 | import { worksans } from "@/app/styles/font"; 2 | import Navbar from "@/components/Navbar"; 3 | 4 | export default function Layout({children} : Readonly<{children: React.ReactNode}>){ 5 | return( 6 |
7 | 8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /on-orbit-frontend/src/components/footer/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | export default function Footer() { 4 | return ( 5 | 6 |