├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.md ├── Code_of_Conduct.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── automated_console.jpg ├── automated_graph.svg ├── conf.py ├── ellipse_fit.png ├── eros_gauss.jpg ├── eros_ls_radec_res.jpg ├── eros_ls_xyz.jpg ├── examples.rst ├── icon.png ├── index.rst ├── introduction.rst ├── iss_2016_gauss.jpg ├── iss_gauss_ls_radec_res.jpg ├── iss_gauss_ls_xyz.jpg ├── make.bat ├── modules.rst └── results.jpg ├── orbitdeterminator ├── __init__.py ├── animate_orbit.py ├── automated.py ├── doppler │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── archive │ │ ├── __init__.py │ │ ├── test_old.py │ │ └── utils_astro.py │ ├── data │ │ ├── data.txt │ │ ├── data_1hz.txt │ │ └── result.txt │ ├── example_batch.py │ ├── example_final.py │ ├── example_tdoa.py │ ├── images │ │ ├── 00_range_range_rate.png │ │ ├── 00_scenario.png │ │ ├── _tdoa_errors.png │ │ ├── _tdoa_measurements.png │ │ ├── _tdoa_results.png │ │ ├── batch_2_1_range_range_rate.png │ │ ├── batch_2_range_range_rate.png │ │ ├── batch_3_range_range_rate.png │ │ ├── batch_4_range_range_rate.png │ │ ├── blue_marble_resized_10.jpg │ │ └── blue_marble_resized_20.jpg │ ├── notebooks │ │ ├── example_batch.ipynb │ │ ├── example_tdoa.ipynb │ │ ├── misc_astropy.ipynb │ │ ├── misc_propagation_difference.ipynb │ │ ├── temp.ipynb │ │ └── test_simualted_data.ipynb │ ├── temp.py │ ├── tests │ │ ├── __init__.py │ │ └── test_utils.py │ └── utils │ │ ├── A clarification regarding the deliverabl.txt │ │ ├── constants.py │ │ ├── utils.py │ │ ├── utils_aux.py │ │ └── utils_vis.py ├── example_data │ ├── 23908_20200316.txt │ ├── DestinationCSV │ │ ├── orbit1_filtered.csv │ │ ├── orbit2_filtered.csv │ │ ├── orbit3_filtered.csv │ │ ├── orbit4_filtered.csv │ │ └── orbit_filtered.csv │ ├── DestinationSVG │ │ ├── orbit.svg │ │ ├── orbit1.svg │ │ ├── orbit1_ellip.svg │ │ ├── orbit1_gibb.svg │ │ ├── orbit1_inter.svg │ │ ├── orbit1_lamb.svg │ │ ├── orbit2.svg │ │ ├── orbit2_ellip.svg │ │ ├── orbit2_gibb.svg │ │ ├── orbit2_inter.svg │ │ ├── orbit2_lamb.svg │ │ ├── orbit3.svg │ │ ├── orbit3_ellip.svg │ │ ├── orbit3_gibb.svg │ │ ├── orbit3_inter.svg │ │ ├── orbit3_lamb.svg │ │ ├── orbit4.svg │ │ ├── orbit4_ellip.svg │ │ ├── orbit4_gibb.svg │ │ ├── orbit4_inter.svg │ │ ├── orbit4_lamb.svg │ │ ├── orbit_ellip.svg │ │ ├── orbit_gibb.svg │ │ ├── orbit_inter.svg │ │ └── orbit_lamb.svg │ ├── ISS.csv │ ├── NOAA-15.csv │ ├── SATOBS-ML-19200716.txt │ ├── SimulatedCSV │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc134.89461080388952_raan112.5156_aop135.0415_ta225.1155_jit0.0_dt1.0_gapno_1502628669.3819425.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc134.89461080388952_raan112.5156_aop135.0415_ta225.1155_jit10000.0_dt1.0_gapno_1502628669.3819425.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc134.89461080388952_raan112.5156_aop135.0415_ta225.1155_jit20000.0_dt1.0_gapno_1502628669.3819425.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc134.89461080388952_raan112.5156_aop135.0415_ta225.1155_jit40000.0_dt1.0_gapno_1502628669.3819425.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc134.89461080388952_raan112.5156_aop135.0415_ta225.1155_jit80000.0_dt1.0_gapno_1502628669.3819425.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc51.6413_raan112.5156_aop135.0415_ta225.1155_jit0.0_dt1.0_gapno_1502634717.3712316.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc51.6413_raan112.5156_aop135.0415_ta225.1155_jit10000.0_dt1.0_gapno_1502634717.3712316.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc51.6413_raan112.5156_aop135.0415_ta225.1155_jit20000.0_dt1.0_gapno_1502634717.3712316.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc51.6413_raan112.5156_aop135.0415_ta225.1155_jit40000.0_dt1.0_gapno_1502634717.3712316.csv │ │ ├── orbit_simulated_a6801000.0_ecc0.000515_inc51.6413_raan112.5156_aop135.0415_ta225.1155_jit80000.0_dt1.0_gapno_1502634717.3712316.csv │ │ ├── orbit_simulated_a7801000.0_ecc0.000515_inc90.0_raan42.0_aop23.0_ta0.0_jit0.0_dt1.0_gapno_1502634593.7599268.csv │ │ ├── orbit_simulated_a7801000.0_ecc0.000515_inc90.0_raan42.0_aop23.0_ta0.0_jit10000.0_dt1.0_gapno_1502634593.7599268.csv │ │ ├── orbit_simulated_a7801000.0_ecc0.000515_inc90.0_raan42.0_aop23.0_ta0.0_jit20000.0_dt1.0_gapno_1502634593.7599268.csv │ │ ├── orbit_simulated_a7801000.0_ecc0.000515_inc90.0_raan42.0_aop23.0_ta0.0_jit40000.0_dt1.0_gapno_1502634593.7599268.csv │ │ └── orbit_simulated_a7801000.0_ecc0.000515_inc90.0_raan42.0_aop23.0_ta0.0_jit80000.0_dt1.0_gapno_1502634593.7599268.csv │ ├── SourceCSV │ │ ├── orbit.csv │ │ ├── orbit1.csv │ │ ├── orbit2.csv │ │ ├── orbit3.csv │ │ └── orbit4.csv │ ├── iod_23908_20200316 │ │ ├── 23908_2020031619.txt │ │ └── 23908_2020031621.txt │ ├── iod_data_af2.txt │ ├── iss_radec_generated_horizons.txt │ ├── mpc_apophis_data.txt │ ├── mpc_ceres_data.txt │ ├── mpc_eros_data.txt │ ├── orbit.csv │ └── sampleukformatdata.txt ├── filters │ ├── __init__.py │ ├── sav_golay.py │ ├── triple_moving_average.py │ └── wiener.py ├── kep_determination │ ├── __init__.py │ ├── custom_prop.py │ ├── ellipse_fit.py │ ├── gauss_example_ceres.py │ ├── gauss_example_earth_sat.py │ ├── gauss_example_eros.py │ ├── gauss_example_iss_jul2016.py │ ├── gauss_method.py │ ├── gibbs_method.py │ ├── interpolation.py │ ├── lamberts_kalman.py │ ├── lamberts_method.py │ ├── least_squares.py │ ├── orbital_elements.py │ ├── positional_observation_reporting.py │ ├── radec_LS_eros.py │ ├── radec_LS_iss_jul2016.py │ └── radec_LS_sat.py ├── main.py ├── optimization │ ├── __init__.py │ └── with_mcmc.py ├── propagation │ ├── cowell.py │ ├── dgsn_simulator.py │ ├── kalman_filter.py │ ├── sgp4.py │ ├── sgp4_prop.py │ ├── sgp4_prop_string.py │ └── simulator.py ├── station_observatory_data │ ├── mpc_observatories.txt │ └── sat_tracking_observatories.txt ├── test_deploy │ ├── README.md │ ├── create_files.py │ ├── deployable.py │ └── src │ │ └── test.txt ├── tests │ ├── __init__.py │ ├── check_sgp4.py │ ├── test_check_keplerian.py │ ├── test_ellipse_fit.py │ ├── test_filters.py │ ├── test_gibbsMethod.py │ ├── test_input_transf.py │ ├── test_kalman.py │ ├── test_lamberts_kalman.py │ ├── test_orbit_trajectory.py │ ├── test_sav_golay.py │ ├── test_sgp4.py │ └── test_statekep_kepstate.py └── util │ ├── __init__.py │ ├── anom_conv.py │ ├── conversion_module.py │ ├── golay_window.py │ ├── input_transf.py │ ├── kep_state.py │ ├── new_tle_kep_state.py │ ├── read_data.py │ ├── rkf5.py │ ├── rkf78.py │ ├── state_kep.py │ ├── teme_to_ecef.py │ └── tle_kep_state.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is taken from: 2 | # https://github.com/github/gitignore/blob/master/Python.gitignore 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | .static_storage/ 60 | .media/ 61 | local_settings.py 62 | 63 | # Pycharm: 64 | .idea 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | 113 | # custom 114 | .cache 115 | orbitdeterminator/propagation/ISS.txt 116 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3.7 3 | 4 | install : 5 | - pip install -r requirements.txt 6 | script : pytest 7 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Developers 3 | ========== 4 | 5 | * Alexandros23Kazantzidis 6 | * nilesh4145 7 | -------------------------------------------------------------------------------- /Code_of_Conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainers at [Zulip Chat](https://aerospaceresearch.zulipchat.com/#). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: https://contributor-covenant.org 46 | [version]: https://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 AerospaceResearch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orbitdeterminator: Automated satellite orbit determination 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9c770ba2dd9d48fa8ba3ac207b9f5c85)](https://www.codacy.com/app/201452004/orbitdeterminator?utm_source=github.com&utm_medium=referral&utm_content=aerospaceresearch/orbitdeterminator&utm_campaign=badger) 4 | [![Build Status](https://travis-ci.org/aerospaceresearch/orbitdeterminator.svg?branch=master)](https://travis-ci.org/aerospaceresearch/orbitdeterminator) 5 | [![Documentation Status](https://readthedocs.org/projects/orbit-determinator/badge/?version=latest)](http://orbit-determinator.readthedocs.io/en/latest/?badge=latest) 6 | [![codecov](https://codecov.io/gh/aerospaceresearch/orbitdeterminator/branch/master/graph/badge.svg)](https://codecov.io/gh/aerospaceresearch/orbitdeterminator) 7 | 8 | ## Quick Start 9 | 10 | __orbitdeterminator__ is a package written in python3 for determining orbit of satellites based on positional data. Various filtering and determination algorithms are available for satellite operators to choose from. 11 | 12 | ## Installation Instructions 13 | 14 | orbitdeterminator runs on Windoes, Linux and MacOs. Some minor adaptions on the presented commands need to be done by you for your OS. 15 | Run the following commands to install orbitdeterminator: 16 | 17 | **Step 1**: Download/Clone the repository 18 | ``` 19 | https://github.com/aerospaceresearch/orbitdeterminator/ 20 | ``` 21 | **Step 2**: Change directory to orbitdeterminator 22 | ``` 23 | cd orbitdeterminator 24 | ``` 25 | **Step 3**: Install required dependencies 26 | ``` 27 | python setup.py install 28 | ``` 29 | or manually install with 30 | ``` 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | ## Contribute: 35 | 36 | [![GitHub pull requests](https://img.shields.io/github/issues-pr/aerospaceresearch/orbitdeterminator.svg?style=for-the-badge)](https://github.com/aerospaceresearch/orbitdeterminator/pulls) 37 | [![GitHub issues](https://img.shields.io/github/issues/aerospaceresearch/orbitdeterminator.svg?style=for-the-badge)](https://github.com/aerospaceresearch/orbitdeterminator/issues) 38 | [![Zulip](https://img.shields.io/badge/Chat-on%20Zulip-17C789.svg?style=for-the-badge)](https://aerospaceresearch.zulipchat.com/#narrow/stream/147024-OrbitDeterminator) 39 | 40 | PRs are welcomed. For contributing to **orbitdeterminator** refer [CONTRIBUTING.md](CONTRIBUTING.md). If there are any issues or ideas they can be addressed through the [issues](https://github.com/aerospaceresearch/orbitdeterminator/issues) or in [chat room](https://aerospaceresearch.zulipchat.com/#narrow/stream/147024-OrbitDeterminator). 41 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = OrbitDeterminator 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/automated_console.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/automated_console.jpg -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Orbit Determinator documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 23 04:28:14 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.viewcode', 38 | 'sphinx.ext.napoleon'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'Orbit Determinator' 54 | copyright = '2018, Nilesh Chaturvedi, Alexandros Kazantzidis' 55 | author = 'Nilesh Chaturvedi, Alexandros Kazantzidis' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = 'v1.0' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '1.0.0' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = False 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = 'sphinx_rtd_theme' 91 | html_logo = 'icon.png' 92 | 93 | # Theme options are theme-specific and customize the look and feel of a theme 94 | # further. For a list of options available for each theme, see the 95 | # documentation. 96 | # 97 | # html_theme_options = {} 98 | 99 | # Add any paths that contain custom static files (such as style sheets) here, 100 | # relative to this directory. They are copied after the builtin static files, 101 | # so a file named "default.css" will overwrite the builtin "default.css". 102 | html_static_path = ['_static'] 103 | 104 | 105 | # -- Options for HTMLHelp output ------------------------------------------ 106 | 107 | # Output file base name for HTML help builder. 108 | htmlhelp_basename = 'OrbitDeterminatordoc' 109 | 110 | 111 | # -- Options for LaTeX output --------------------------------------------- 112 | 113 | latex_elements = { 114 | # The paper size ('letterpaper' or 'a4paper'). 115 | # 116 | # 'papersize': 'letterpaper', 117 | 118 | # The font size ('10pt', '11pt' or '12pt'). 119 | # 120 | # 'pointsize': '10pt', 121 | 122 | # Additional stuff for the LaTeX preamble. 123 | # 124 | # 'preamble': '', 125 | 126 | # Latex figure (float) alignment 127 | # 128 | # 'figure_align': 'htbp', 129 | } 130 | 131 | # Grouping the document tree into LaTeX files. List of tuples 132 | # (source start file, target name, title, 133 | # author, documentclass [howto, manual, or own class]). 134 | latex_documents = [ 135 | (master_doc, 'OrbitDeterminator.tex', 'Orbit Determinator Documentation', 136 | 'Nilesh Chaturvedi, Alexandros Kazantzidis', 'manual'), 137 | ] 138 | 139 | 140 | # -- Options for manual page output --------------------------------------- 141 | 142 | # One entry per manual page. List of tuples 143 | # (source start file, name, description, authors, manual section). 144 | man_pages = [ 145 | (master_doc, 'orbitdeterminator', 'Orbit Determinator Documentation', 146 | [author], 1) 147 | ] 148 | 149 | 150 | # -- Options for Texinfo output ------------------------------------------- 151 | 152 | # Grouping the document tree into Texinfo files. List of tuples 153 | # (source start file, target name, title, author, 154 | # dir menu entry, description, category) 155 | texinfo_documents = [ 156 | (master_doc, 'OrbitDeterminator', 'Orbit Determinator Documentation', 157 | author, 'OrbitDeterminator', 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | # Example configuration for intersphinx: refer to the Python standard library. 162 | intersphinx_mapping = {'https://docs.python.org/': None} 163 | -------------------------------------------------------------------------------- /docs/ellipse_fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/ellipse_fit.png -------------------------------------------------------------------------------- /docs/eros_gauss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/eros_gauss.jpg -------------------------------------------------------------------------------- /docs/eros_ls_radec_res.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/eros_ls_radec_res.jpg -------------------------------------------------------------------------------- /docs/eros_ls_xyz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/eros_ls_xyz.jpg -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/icon.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Orbit Determinator documentation master file, created by 2 | sphinx-quickstart on Fri Jun 23 04:28:14 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | ++++++++++++++++++++++++++++++++++++++++++++++ 6 | Welcome to Orbit Determinator's documentation! 7 | ++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | ======================== 10 | About Orbit Determinator 11 | ======================== 12 | 13 | The orbitdeterminator package provides tools to compute the orbit of a satellite from positional 14 | measurements. It supports both cartesian and spherical coordinates for the initial positional data, two filters 15 | for smoothing and removing errors from the initial data set and finally two methods for preliminary orbit determination. 16 | The package is labeled as an open source scientific package and can be helpful for projects concerning space orbit 17 | tracking. 18 | 19 | Lots of university students build their own cubesat's and set them into space orbit, lots of researchers 20 | start building their own ground station to track active satellite missions. For those particular space enthusiasts 21 | we suggest using and trying our package. Any feedback is more than welcome and we wish our work to inspire other's to 22 | join us and add more helpful features. 23 | 24 | Our future goals for the package is to add a 3d visual graph of the final computed satellite orbit, add more 25 | filters, methods and with the help of a tracking ground station to build a server system that computes orbital elements 26 | for many active satellite missions. 27 | 28 | ===================== 29 | Copyright and License 30 | ===================== 31 | 32 | The project's idea belongs to AerospaceResearch.net and Andreas Hornig and it has been developed under 33 | Google summer of code 2017 by Nilesh Chaturvedi and Alexandros Kazantzidis. 34 | 35 | It is distributed under an open-source MIT license. Please find `LICENSE` in top level directory for details. 36 | 37 | ============ 38 | Installation 39 | ============ 40 | 41 | Open up your control panel, pip install git if you do not already have it and then clone the github repository of the 42 | program https://github.com/aerospaceresearch/orbitdeterminator. Create a new virtual environment for python version 3.4. 43 | Then, all you need to do is go to the directory where the package has been cloned with cd orbitdeterminator and 44 | run **python setup.py install**. That should install the package into your Lib/site-packages and you will be able to 45 | import and use it. Other than import you can just use it immediately from the clone directory (preferred). 46 | 47 | .. toctree:: 48 | :maxdepth: 2 49 | :caption: Contents: 50 | 51 | modules.rst 52 | examples.rst 53 | 54 | Indices and tables 55 | ================== 56 | 57 | * :ref:`genindex` 58 | * :ref:`modindex` 59 | * :ref:`search` 60 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | Package info 2 | ============ 3 | 4 | The orbitdeterminator package provides tools to compute the orbit of a satellite from positional 5 | measurements. It supports both cartesian and spherical coordinates for the initial positional data, two filters 6 | for smoothing and removing errors from the initial data set and finally two methods for preliminary orbit determination. 7 | The package is labeled as an open source scientific package and can be helpful for projects concerning space orbit 8 | tracking. 9 | 10 | Lots of university students build their own cubesat's and set them into space orbit, lots of researchers 11 | start building their own ground station to track active satellite missions. For those particular space enthusiasts 12 | we suggest using and trying our package. Any feedback is more than welcome and we wish our work to inspire other's to 13 | join us and add more helpful features. 14 | 15 | Our future goals for the package is to add a 3d visual graph of the final computed satellite orbit, add more 16 | filters, methods and with the help of a tracking ground station to build a server system that computes orbital elements 17 | for many active satellite missions. 18 | 19 | The project's idea belongs to AerospaceResearch.net and Andreas Hornig and it has been carried out within 2017 20 | Google summer of code by Nilesh Chaturvedi and Alexandros Kazantzidis. It is also an open source projects covered by 21 | MIT licence. 22 | -------------------------------------------------------------------------------- /docs/iss_2016_gauss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/iss_2016_gauss.jpg -------------------------------------------------------------------------------- /docs/iss_gauss_ls_radec_res.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/iss_gauss_ls_radec_res.jpg -------------------------------------------------------------------------------- /docs/iss_gauss_ls_xyz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/iss_gauss_ls_xyz.jpg -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=OrbitDeterminator 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | Modules documentation 2 | ===================== 3 | 4 | Filters: 5 | -------- 6 | 7 | Triple Moving Average 8 | ~~~~~~~~~~~~~~~~~~~~~ 9 | .. automodule:: orbitdeterminator.filters.triple_moving_average 10 | :members: 11 | 12 | Savintzky - Golay 13 | ~~~~~~~~~~~~~~~~~ 14 | .. automodule:: orbitdeterminator.filters.sav_golay 15 | :members: 16 | 17 | Interpolation: 18 | -------------- 19 | 20 | Lamberts-Kalman Method 21 | ~~~~~~~~~~~~~~~~~~~~~~ 22 | .. automodule:: orbitdeterminator.kep_determination.lamberts_kalman 23 | :members: 24 | 25 | Gibb's Method 26 | ~~~~~~~~~~~~~ 27 | .. autoclass:: orbitdeterminator.kep_determination.gibbsMethod.Gibbs 28 | :members: 29 | 30 | Spline Interpolation 31 | ~~~~~~~~~~~~~~~~~~~~ 32 | .. automodule:: orbitdeterminator.kep_determination.interpolation 33 | :members: 34 | 35 | Ellipse Fit 36 | ~~~~~~~~~~~~~~~~~~~~ 37 | .. automodule:: orbitdeterminator.kep_determination.ellipse_fit 38 | :members: 39 | 40 | Gauss method 41 | ~~~~~~~~~~~~~~~~~~~~ 42 | .. automodule:: orbitdeterminator.kep_determination.gauss_method 43 | :members: 44 | 45 | Least squares 46 | ~~~~~~~~~~~~~~~~~~~~ 47 | .. automodule:: orbitdeterminator.kep_determination.least_squares 48 | :members: 49 | 50 | Propagation: 51 | ------------ 52 | 53 | Propagation Model 54 | ~~~~~~~~~~~~~~~~~ 55 | .. autoclass:: orbitdeterminator.propagation.sgp4.SGP4 56 | :special-members: __init__ 57 | :members: 58 | 59 | .. autoclass:: orbitdeterminator.propagation.sgp4.FlagCheckError 60 | :members: 61 | 62 | Cowell Method 63 | ~~~~~~~~~~~~~~~~~ 64 | .. automodule:: orbitdeterminator.propagation.cowell 65 | :members: 66 | 67 | Simulator 68 | ~~~~~~~~~~~~~~~~~ 69 | .. autoclass:: orbitdeterminator.propagation.simulator.Simulator 70 | :special-members: __init__ 71 | :members: 72 | 73 | .. autoclass:: orbitdeterminator.propagation.simulator.SimParams 74 | :members: 75 | 76 | .. autoclass:: orbitdeterminator.propagation.simulator.OpWriter 77 | :members: 78 | 79 | .. autoclass:: orbitdeterminator.propagation.simulator.print_r 80 | :show-inheritance: 81 | 82 | .. autoclass:: orbitdeterminator.propagation.simulator.save_r 83 | :show-inheritance: 84 | :members: __init__ 85 | 86 | 87 | DGSN Simulator 88 | ~~~~~~~~~~~~~~~~~ 89 | .. autoclass:: orbitdeterminator.propagation.dgsn_simulator.DGSNSimulator 90 | :special-members: __init__ 91 | :members: 92 | 93 | .. autoclass:: orbitdeterminator.propagation.dgsn_simulator.SimParams 94 | :members: 95 | 96 | .. autoclass:: orbitdeterminator.propagation.dgsn_simulator.OpWriter 97 | :members: 98 | 99 | .. autoclass:: orbitdeterminator.propagation.dgsn_simulator.print_r 100 | :show-inheritance: 101 | 102 | .. autoclass:: orbitdeterminator.propagation.dgsn_simulator.save_r 103 | :show-inheritance: 104 | :members: __init__ 105 | 106 | Kalman Filter 107 | ~~~~~~~~~~~~~~~~~ 108 | .. automodule:: orbitdeterminator.propagation.kalman_filter 109 | :members: 110 | 111 | sgp4_prop 112 | ~~~~~~~~~~~~~~~~~ 113 | .. automodule:: orbitdeterminator.propagation.sgp4_prop 114 | :members: 115 | 116 | sgp4_prop_string 117 | ~~~~~~~~~~~~~~~~~ 118 | .. automodule:: orbitdeterminator.propagation.sgp4_prop_string 119 | :members: 120 | 121 | Utils: 122 | ------ 123 | 124 | kep_state 125 | ~~~~~~~~~ 126 | .. automodule:: orbitdeterminator.util.kep_state 127 | :members: 128 | 129 | read_data 130 | ~~~~~~~~~ 131 | .. automodule:: orbitdeterminator.util.read_data 132 | :members: 133 | 134 | state_kep 135 | ~~~~~~~~~ 136 | .. automodule:: orbitdeterminator.util.state_kep 137 | :members: 138 | 139 | input_transf 140 | ~~~~~~~~~~~~ 141 | .. automodule:: orbitdeterminator.util.input_transf 142 | :members: 143 | 144 | rkf78 145 | ~~~~~ 146 | .. automodule:: orbitdeterminator.util.rkf78 147 | :members: 148 | 149 | golay_window 150 | ~~~~~~~~~~~~ 151 | .. automodule:: orbitdeterminator.util.golay_window 152 | :members: 153 | 154 | anom_conv 155 | ~~~~~~~~~~~~ 156 | .. automodule:: orbitdeterminator.util.anom_conv 157 | :members: 158 | 159 | new_tle_kep_state 160 | ~~~~~~~~~~~~~~~~~ 161 | .. automodule:: orbitdeterminator.util.new_tle_kep_state 162 | :members: 163 | 164 | teme_to_ecef 165 | ~~~~~~~~~~~~ 166 | .. automodule:: orbitdeterminator.util.teme_to_ecef 167 | :members: 168 | -------------------------------------------------------------------------------- /docs/results.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/docs/results.jpg -------------------------------------------------------------------------------- /orbitdeterminator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/animate_orbit.py: -------------------------------------------------------------------------------- 1 | 2 | from vpython import * 3 | import math 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import time 7 | from matplotlib.animation import FuncAnimation 8 | import random 9 | import math 10 | from mpl_toolkits import mplot3d 11 | from util import (read_data, kep_state, rkf78, golay_window) 12 | from filters import (sav_golay, triple_moving_average,wiener) 13 | from kep_determination import (lamberts_kalman, interpolation, ellipse_fit, gibbs_method) 14 | 15 | ''' 16 | This is a module to animate orbit of a satellite around a planet of given radius r and data file. 17 | ''' 18 | 19 | 20 | 21 | 22 | 23 | 24 | #data='example_data/orbit.csv' 25 | def read(data_file): 26 | #global data 27 | data = read_data.load_data(data_file) 28 | data[:, 1:4] = data[:, 1:4] / 1000 29 | data_after_filter = wiener.wiener_new(data,3) 30 | return data_after_filter 31 | 32 | def animate(data_file,r): 33 | print("Do you want to animate orbit Y/N") 34 | choice=input() 35 | if(choice in 'Yy'): 36 | i=0 37 | data_after_filter=read(data_file) 38 | c=data_after_filter.shape[0] 39 | Earth=sphere(pos=vector(0,0,0), radius=r, color=color.red) 40 | satellite=sphere(pos=vector(data_after_filter[i][1],data_after_filter[i][2],data_after_filter[i][3]), radius=100, color=color.blue,make_trail=True, trail_type="points", 41 | interval=3, retain=30,trail_color=color.green,trail_radius=100) 42 | k=100 43 | 44 | while True: 45 | rate(500) 46 | satellite.pos.x=data_after_filter[i%c][1] 47 | satellite.pos.y=data_after_filter[i%c][2] 48 | satellite.pos.z=data_after_filter[i%c][3] 49 | i+=1 50 | #print(satellite.pos.x,satellite.pos.y,satellite.pos.z) 51 | else: 52 | return 0 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /orbitdeterminator/automated.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Server Version of Main.py 3 | Runs the whole process in one file 4 | Input a .csv positional data file (time, x, y, z) and this script generates the final set of keplerian elements 5 | along with a plot and a filtered csv data file. Both the generated results lie in a folder named dst. 6 | ''' 7 | 8 | import os 9 | import time 10 | import sys 11 | import numpy as np 12 | import matplotlib as mpl 13 | from subprocess import (PIPE, run) 14 | import matplotlib.pylab as plt 15 | 16 | from util import (read_data, kep_state, rkf78, golay_window) 17 | from filters import (sav_golay, triple_moving_average) 18 | from kep_determination import (lamberts_kalman, interpolation, gibbs_method, ellipse_fit) 19 | from propagation import sgp4 20 | 21 | 22 | SOURCE_ABSOLUTE = os.getcwd() + "/example_data/SourceCSV" # Absolute path of source directory 23 | print("Do you wish to reset(deinit/init) git repository? [y/n]") 24 | user_input1 = input() 25 | if(user_input1 == "y" or user_input1 == "Y"): 26 | os.system("cd %s; rm -rf .git && rm -rf .gitignore" % (SOURCE_ABSOLUTE)) 27 | os.system("cd %s; git init" % (SOURCE_ABSOLUTE)) 28 | 29 | 30 | def untracked_files(): 31 | ''' 32 | Finds untracked/unprocessed files in the source directory. 33 | 34 | Returns: 35 | res (string): List of untracked files. 36 | ''' 37 | 38 | res = run( 39 | "cd %s ; git status" % (SOURCE_ABSOLUTE), 40 | stdout=PIPE, stderr=PIPE, 41 | universal_newlines=True, 42 | shell=True 43 | ) 44 | result = [line.strip() for line in res.stdout.split("\n")] 45 | 46 | files = [file 47 | for file in result if (file.endswith(".csv") 48 | and not (file.startswith("new file") or 49 | file.startswith("deleted") or file.startswith("modified")))] 50 | return files 51 | 52 | def stage(processed): 53 | ''' 54 | Stage the processed files into git file system 55 | 56 | Agrs: 57 | processed (list): List of processed files. 58 | ''' 59 | for file in processed: 60 | print("staging") 61 | run( 62 | "cd %s;git add '%s'" % (SOURCE_ABSOLUTE, file), 63 | stdout=PIPE, stderr=PIPE, 64 | universal_newlines=True, 65 | shell=True 66 | ) 67 | print("File %s has been staged." % (file)) 68 | 69 | 70 | def process(data_file, error_apriori, name): 71 | ''' Perform filtering and orbit determination methods. 72 | Applies filters and orbit determination techniques on the input data and saves the 73 | output in dst folder. 74 | 75 | Args: 76 | data_file (numpy array): Raw orbit data 77 | error_apriori (float): apriori estimation of the measurements error in km 78 | name (str): name of the file being processed 79 | ''' 80 | # Get positional data 81 | data = data_file 82 | 83 | # Units is km by default 84 | 85 | # Apply the Triple moving average filter with window = 3 86 | data_after_filter = triple_moving_average.generate_filtered_data(data, 3) 87 | 88 | # Use the golay_window.py script to find the window for the savintzky golay filter based on the error you input 89 | window = golay_window.window(error_apriori, data_after_filter) 90 | 91 | # Apply the Savintzky - Golay filter with window = 31 and polynomail parameter = 6 92 | data_after_filter = sav_golay.golay(data_after_filter, window, 3) 93 | 94 | # Compute the residuals between filtered data and initial data and then the sum and mean values of each axis 95 | res = data_after_filter[:, 1:4] - data[:, 1:4] 96 | sums = np.sum(res, axis=0) 97 | print("Displaying the sum of the residuals for each axis") 98 | print(sums) 99 | print(" ") 100 | 101 | means = np.mean(res, axis=0) 102 | print("Displaying the mean of the residuals for each axis") 103 | print(means) 104 | print(" ") 105 | 106 | # Save the filtered data into a new csv called "filtered" 107 | np.savetxt(os.getcwd() + "/example_data/DestinationCSV/" + "%s_filtered.csv" % (name), data_after_filter, delimiter=",") 108 | 109 | # Apply Lambert's solution for the filtered data set 110 | kep_lamb = lamberts_kalman.create_kep(data_after_filter) 111 | 112 | # Apply the interpolation method 113 | kep_inter = interpolation.main(data_after_filter) 114 | 115 | # Apply the Gibbs method 116 | kep_gibbs = gibbs_method.gibbs_get_kep(data_after_filter[:,1:]) 117 | 118 | # Apply the ellipse best fit method 119 | kep_ellip = ellipse_fit.determine_kep(data_after_filter[:,1:])[0] 120 | 121 | # Apply Kalman filters to find the best approximation of the keplerian elements for all solutions 122 | # set we a estimate of measurement vatiance R = 0.01 ** 2 123 | kep_final_lamb = lamberts_kalman.kalman(kep_lamb, 0.01 ** 2) 124 | kep_final_lamb = np.transpose(kep_final_lamb) 125 | kep_final_lamb = np.resize(kep_final_lamb, ((7, 1))) 126 | kep_final_lamb[6, 0] = sgp4.rev_per_day(kep_final_lamb[0, 0]) 127 | 128 | kep_final_inter = lamberts_kalman.kalman(kep_inter, 0.01 ** 2) 129 | kep_final_inter = np.transpose(kep_final_inter) 130 | kep_final_inter = np.resize(kep_final_inter, ((7, 1))) 131 | kep_final_inter[6, 0] = sgp4.rev_per_day(kep_final_inter[0, 0]) 132 | 133 | kep_final_ellip = np.transpose(kep_ellip) 134 | kep_final_ellip = np.resize(kep_final_ellip, ((7, 1))) 135 | kep_final_ellip[6, 0] = sgp4.rev_per_day(kep_final_ellip[0, 0]) 136 | 137 | kep_final_gibbs = lamberts_kalman.kalman(kep_gibbs, 0.01 ** 2) 138 | kep_final_gibbs = np.transpose(kep_final_gibbs) 139 | kep_final_gibbs = np.resize(kep_final_gibbs, ((7, 1))) 140 | kep_final_gibbs[6, 0] = sgp4.rev_per_day(kep_final_gibbs[0, 0]) 141 | 142 | kep_final = np.zeros((7, 4)) 143 | kep_final[:, 0] = np.ravel(kep_final_lamb) 144 | kep_final[:, 1] = np.ravel(kep_final_inter) 145 | kep_final[:, 2] = np.ravel(kep_final_ellip) 146 | kep_final[:, 3] = np.ravel(kep_final_gibbs) 147 | 148 | # Print the final orbital elements for all solutions 149 | kep_elements = ["Semi major axis (a)(km)", "Eccentricity (e)", "Inclination (i)(deg)", "Argument of perigee (ω)(deg)", "Right acension of ascending node (Ω)(deg)", "True anomaly (v)(deg)", "Frequency (f)(rev/day)"] 150 | det_methods = ["Lamberts Kalman", "Spline Interpolation", "Ellipse Best Fit", "Gibbs 3 Vector"] 151 | method_name = ["lamb", "inter", "ellip", "gibb"] 152 | 153 | for i in range(0, 4): 154 | print("\n******************Output for %s Method******************\n" % det_methods[i]) 155 | j = 0 156 | for j in range(0, 7): 157 | print("%s: %.16f\n" % (kep_elements[j], kep_final[j, i])) 158 | 159 | print("\nSave plots? [y/n]") 160 | user_input = input() 161 | 162 | if(user_input == "y" or user_input == "Y"): 163 | for j in range(0, 4): 164 | # Plot the initial data set, the filtered data set and the final orbit 165 | # First we transform the set of keplerian elements into a state vector 166 | state = kep_state.kep_state(np.resize(kep_final[:, j], (7, 1))) 167 | 168 | # Then we produce more state vectors at varius times using a Runge Kutta algorithm 169 | keep_state = np.zeros((6, 150)) 170 | ti = 0.0 171 | tf = 1.0 172 | t_hold = np.zeros((150, 1)) 173 | x = state 174 | h = 0.1 175 | tetol = 1e-04 176 | for i in range(0, 150): 177 | keep_state[:, i] = np.ravel(rkf78.rkf78(6, ti, tf, h, tetol, x)) 178 | t_hold[i, 0] = tf 179 | tf = tf + 1 180 | 181 | positions = keep_state[0:3, :] 182 | 183 | ## Finally we plot the graph 184 | mpl.rcParams['legend.fontsize'] = 10 185 | fig = plt.figure() 186 | ax = fig.gca(projection='3d') 187 | ax.plot(data[:, 1], data[:, 2], data[:, 3], ".", label='Initial data ') 188 | ax.plot(data_after_filter[:, 1], data_after_filter[:, 2], data_after_filter[:, 3], "k", linestyle='-', 189 | label='Filtered data') 190 | ax.plot(positions[0, :], positions[1, :], positions[2, :], "r-", label='Orbit after %s method' % det_methods[j]) 191 | ax.legend() 192 | ax.can_zoom() 193 | ax.set_xlabel('x (km)') 194 | ax.set_ylabel('y (km)') 195 | ax.set_zlabel('z (km)') 196 | plt.savefig(os.getcwd() + "/example_data/DestinationSVG/" + '%s_%s.svg'%(name, method_name[j]), format="svg") 197 | print("saved %s_%s.svg"%(name, method_name[j])) 198 | 199 | def main(): 200 | 201 | number_untracked = 0 202 | while True: 203 | raw_files = untracked_files() 204 | if not raw_files: 205 | if (number_untracked == 0): 206 | print("\nNo unprocessed file found in ./example_data/SourceCSV folder") 207 | else: 208 | print("\nAll untracked files have been processed") 209 | print("Add new files in ./example_data/SourceCSV folder to process them") 210 | time_elapsed = 0 211 | timeout = 30 212 | while (time_elapsed <= timeout and not raw_files): 213 | sys.stdout.write("\r") 214 | sys.stdout.write("-> Timeout in - {:2d} s".format(timeout - time_elapsed)) 215 | sys.stdout.flush() 216 | time.sleep(1) 217 | time_elapsed += 1 218 | raw_files = untracked_files() 219 | sys.stdout.write("\r \n") 220 | pass 221 | if raw_files: 222 | number_untracked += len(raw_files) 223 | for file in raw_files: 224 | print("processing") 225 | a = read_data.load_data(SOURCE_ABSOLUTE + "/" + file) 226 | 227 | process(a, 10.0, str(file)[:-4]) 228 | print("File : %s has been processed \n \n" % file) 229 | stage(raw_files) 230 | continue 231 | print("No new unprocessed file was added, program is now exiting due to timeout!") 232 | print("Total {} untracked files were processed".format(number_untracked)) 233 | break 234 | 235 | if __name__ == "__main__": 236 | main() 237 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | .vscode/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/README.md: -------------------------------------------------------------------------------- 1 | # gsoc-2020-temp -------------------------------------------------------------------------------- /orbitdeterminator/doppler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/doppler/archive/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/archive/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/doppler/archive/test_old.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | 4 | from .utils_astro import * 5 | 6 | class TestTransformations(unittest.TestCase): 7 | """ Unit test for coordinate system transofrmations. 8 | """ 9 | 10 | @classmethod 11 | def setUpClass(cls) -> None: 12 | 13 | # Reference JD, 01/01/2020 00:00:00 14 | cls.date_0 = np.array([2000, 1, 1, 0, 0, 0]) 15 | cls.date_1 = np.array([2018, 3, 22, 6, 58, 0.5]) 16 | cls.date_2 = np.array([2020, 4, 15, 6, 30, 30]) 17 | 18 | # Chilbolton 19 | cls.geodetic_0 = np.array([np.deg2rad(51.1483578), np.deg2rad(-1.4384458), 81.0]) 20 | # Atlanta 21 | cls.geodetic_1 = np.array([np.deg2rad(33.7743331), np.deg2rad(-84.3970209), 288.0]) 22 | 23 | cls.eci_1 = np.zeros((6,1)) 24 | cls.eci_2 = np.zeros((6,1)) 25 | 26 | cls.ecef_1 = np.zeros((6,1)) 27 | cls.ecef_2 = np.zeros((6,1)) 28 | 29 | def test_jd(self): 30 | """ Test Julian Day Number 31 | """ 32 | 33 | # Reference JD case 34 | jd_0, jdfrac_0 = get_jd(self.date_0) 35 | self.assertEqual(jd_0, 2451544.5) 36 | self.assertEqual(jdfrac_0, 0) 37 | 38 | # Test #1, slight difference w/ MATLAB results, 14 sig. digits 39 | jd_1, jdfrac_1 = get_jd(self.date_1) 40 | self.assertEqual(jd_1, 2458199.5) 41 | self.assertAlmostEqual(jdfrac_1, 0.290283564814815) 42 | #print(f"\n{jdfrac_1}") 43 | #print(0.290283564814815) 44 | 45 | jd_2, jdfrac_2 = get_jd(self.date_2) 46 | self.assertEqual(jd_2, 2458954.5) 47 | self.assertAlmostEqual(jdfrac_2, 0.271180555555556) 48 | #print(f"\n{jdfrac_2}") 49 | #print(0.271180555555556) 50 | 51 | def test_gmst(self): 52 | """ Test Greenwitch Mean Sidereal Time 53 | """ 54 | 55 | # Test #0 56 | # MATLAB 14 significant digits 57 | jd_0, jdfrac_0 = get_jd(self.date_0) 58 | test_gmst_0 = get_gmst(jd_0 + jdfrac_0) 59 | self.assertAlmostEqual(test_gmst_0, 1.744767163330613) 60 | #print(f"\n{test_gmst_0}") 61 | #print(1.744767163330613) 62 | 63 | # Test #1 64 | # MATLAB 11 significant digits 65 | jd_1, jdfrac_1 = get_jd(self.date_1) 66 | test_gmst_1 = get_gmst(jd_1 + jdfrac_1) 67 | self.assertAlmostEqual(test_gmst_1, 4.960910441081389) 68 | #print(f"\n{test_gmst_1}") 69 | #print(4.96091044108139) 70 | 71 | # Test #2 72 | # MATLAB 11 significant digits 73 | jd_2, jdfrac_2 = get_jd(self.date_2) 74 | test_gmst_2 = get_gmst(jd_2 + jdfrac_2) 75 | self.assertAlmostEqual(test_gmst_2, 5.26229132147851) 76 | #print(f"\n{test_gmst_2}") 77 | #print(5.26229132147851) 78 | 79 | def test_geodetic_to_ecef(self): 80 | """ Test latitude, longitude, altitude to Earth-Centered, Earth-Fixed (ECEF) 81 | transformation 82 | """ 83 | 84 | # Test #1, Chilbolton 85 | test_ecef_0 = geodetic_to_ecef(self.geodetic_0) 86 | np.testing.assert_allclose(test_ecef_0, 87 | np.array([4007978.00308211, -100643.89986322, 4943977.86517641, 0, 0 ,0])) 88 | # print(f"\n{test_ecef_0}") 89 | 90 | # Test #1, Atlanta 91 | test_ecef_1 = geodetic_to_ecef(self.geodetic_1) 92 | np.testing.assert_allclose(test_ecef_1, 93 | np.array([518191.396915951, -5282096.72284627, 3525827.47644486, 0, 0 ,0])) 94 | # print(f"\n{test_ecef_1}") 95 | 96 | pass 97 | 98 | def test_ecef_to_pef(self): 99 | """ Test Earth-Centered, Earth-Fixed frame to Pseudo Earth-Centered Fixed (PEF) frame """ 100 | 101 | # Test zero rotation 102 | test_ecef_0 = geodetic_to_ecef(self.geodetic_0) 103 | test_pef_0 = ecef_to_pef(test_ecef_0, 0, 0, 0) 104 | np.testing.assert_allclose(test_ecef_0, test_pef_0) 105 | 106 | # Test some precession 107 | test_pef_1 = ecef_to_pef(test_ecef_0, 0, 0.1, 0.1) 108 | pef_1_matlab = np.array([3494380.60530255, 430913.646165595, 5302881.44445618, 0, 0, 0]) 109 | np.testing.assert_allclose(test_ecef_0, test_pef_0) 110 | 111 | # TODO: More tests 112 | 113 | def test_polar_motion(self): 114 | # Test 0 115 | # Zero polar motion coefficients 116 | xp_0, yp_0 = 0, 0 117 | pm_0 = polar_motion(xp_0, yp_0) 118 | np.testing.assert_allclose(pm_0, np.eye(3,3)) 119 | 120 | # Test 1, IAU-76/FK5 121 | xp_1, yp_1 = 0.1, 0.12 122 | pm_1 = polar_motion(xp_1, yp_1, ttt=0, type='iau-76') 123 | pm_1_matlab = np.array([ 124 | [0.995004165278026, 0, -0.0998334166468282], 125 | [0.0119512786679861, 0.992808635853866, 0.119114144887101], 126 | [0.0991154781937681, -0.119712207288919, 0.987848727998592] 127 | ]) 128 | np.testing.assert_allclose(pm_1, pm_1_matlab) 129 | 130 | # Test 2, IAU-76/FK5 131 | xp_2, yp_2 = 0.02, 0.01 132 | pm_2 = polar_motion(xp_2, yp_2, ttt=0, type='iau-76') 133 | pm_2_matlab = np.array([ 134 | [0.999800006666578, 0, -0.0199986666933331], 135 | [0.000199983333838881, 0.999950000416665, 0.0099978334341645], 136 | [0.0199976667683312, -0.00999983333416666, 0.999750017082826] 137 | ]) 138 | np.testing.assert_allclose(pm_2, pm_2_matlab) 139 | 140 | # TODO: Test IAU-2000 polar motion 141 | 142 | def test_ecef_to_teme(self): 143 | """ Test Earth-Centered, Earth-Fixed (ECEF) to True Equator, Mean Equinox (TEME) 144 | coordinate system transformation. 145 | """ 146 | 147 | # Test - ground site 148 | jd_1, jdfrac_1 = get_jd(self.date_1) 149 | ttt_1 = get_ttt(jd_1 + jdfrac_1) 150 | ecef_1 = geodetic_to_ecef(self.geodetic_0) 151 | test_teme_1 = ecef_to_teme(ecef_1, jd_1+jdfrac_1, ttt_1, 0, 0, 0, 0) 152 | 153 | teme_1_matlab = np.array([ 154 | 888294.982007313, -3909597.02433772, 4943977.86517641, 155 | 0285.092316786936, 0064.7754929303933, 0 156 | ]) 157 | 158 | print(test_teme_1) 159 | print(teme_1_matlab) 160 | 161 | np.testing.assert_allclose(test_teme_1, teme_1_matlab) 162 | 163 | #test_ecef_1 = geodetic_to_ecef(self.geodetic_0) 164 | 165 | 166 | # def test_ecef_to_eci(self): 167 | # """ Test Earth-Centered, Earth-Fixed (ECEF) to Earth-Centered Inertial (ECI) 168 | # coordinate system transformation. 169 | # """ 170 | 171 | # # Test #1 172 | # jd_1, jdfrac_1 = get_jd(self.date_1) 173 | # test_ecef_1 = geodetic_to_ecef(self.geodetic_0) 174 | # test_eci_1 = ecef_to_eci(test_ecef_1, jd_1 + jdfrac_1) 175 | # #print(test_eci_1) 176 | 177 | # #test_eci_1 = ecef_to_eci(self.ecef_1, get_jd(self.date_1)) 178 | 179 | # # Test #2 180 | # #test_eci_2 = ecef_to_eci(self.ecef_2, get_jd(self.date_2)) 181 | 182 | # pass 183 | 184 | def test_eci_to_ecef(self): 185 | """ Test Earth-Centered Inertial (ECI) to Earth-Centered, Earth-Fixed (ECEF) 186 | coordinate system transformation. 187 | """ 188 | 189 | # Test #1 190 | #test_ecef_1 = ecef_to_eci(self.eci_1, get_jd(self.date_1)) 191 | 192 | # Test #2 193 | #test_ecef_2 = ecef_to_eci(self.eci_2, get_jd(self.date_2)) 194 | 195 | pass 196 | 197 | if __name__ == "__main__": 198 | unittest.main() -------------------------------------------------------------------------------- /orbitdeterminator/doppler/archive/utils_astro.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import fmod, pi, floor, sqrt 3 | 4 | from orbitdeterminator.doppler.utils.constants import * 5 | 6 | # def get_jd(date): 7 | # """ Obtain Julian Day given date. 8 | 9 | # Args: 10 | # date (datetime): date to be converted 11 | # Returns 12 | # jd (float): julian day number 13 | # """ 14 | # y = date[0] 15 | # m = date[1] 16 | 17 | # if m == 1 or m == 2: 18 | # m = m + 12 19 | # y = y - 1 20 | 21 | # t = floor(y * 0.01) 22 | # b = 2 - t + floor(t * 0.25) 23 | # c = ((date[5] / 60.0 + date[4]) / 60.0 + date[3]) / 24.0 24 | 25 | # jd = floor(365.25 * (y + 4716.0)) + floor(30.6001 * (m + 1)) \ 26 | # + date[2] + b - 1524.5 + c 27 | 28 | # return jd 29 | 30 | def get_jd(date): 31 | """ Obtain Julian Day given date. 32 | 33 | Args: 34 | date (datetime): date to be converted 35 | Returns 36 | jd (float): julian day number 37 | """ 38 | 39 | jd = 367.0 * date[0] \ 40 | - np.floor( (7 * (date[0] + floor( (date[1] + 9) / 12.0) ) ) * 0.25 ) \ 41 | + np.floor( 275 * date[1] / 9.0 ) + date[2] + 1721013.5 42 | jdfrac = (date[5] + date[4] * 60.0 + date[3] * 3600.0) / 86400.0 43 | 44 | if jdfrac > 1.0: 45 | jd = jd + np.floor(jdfrac) 46 | jdfrac = jdfrac - np.floor(jdfrac) 47 | 48 | return jd, jdfrac 49 | 50 | def get_ttt(jd:float): 51 | """ Get Julian centuries. 52 | 53 | Args: 54 | jd (float): Julan day number. 55 | Returns: 56 | (float): Julian centuries. 57 | """ 58 | return (jd - 2451545.0) / 36525.0 59 | 60 | def get_gmst(jd_ut_1): 61 | """ Obtain Greenwitch Mean Sidereal Time (GMST). 62 | 63 | Args: 64 | jjd_ut_1d (float):Julian Day Number 65 | Returns: 66 | theta_gmst (float): Greenwitch Mean Sidereal Time 67 | """ 68 | 69 | t_ut_1 = (jd_ut_1 - 2451545.0) / 36525.0 70 | theta_gmst = -6.2e-6 * t_ut_1**3 + 0.093104 * t_ut_1**2 \ 71 | + (876600.0 * 3600.0 + 8640184.812866) * t_ut_1 + 67310.54841 72 | theta_gmst = fmod(theta_gmst / 240.0 * pi / 180.0, 2.0*pi) 73 | 74 | if theta_gmst < 0.0: 75 | theta_gmst = theta_gmst + 2.0*pi 76 | 77 | return theta_gmst 78 | 79 | # def eci_to_ecef(eci, jd): 80 | # """ Earth-Centered Inertial (ECI) to Earth-Centered, Earth-Fixed (ECEF) 81 | # coordinate system conversion. 82 | 83 | # TODO: Vectorize 84 | 85 | # Args: 86 | # eci (np.array): Vector in ECI coordinate system (pos, velocity) 87 | # jd (float): Julian Day Number 88 | # Returns: 89 | # ecef (np.array): Vector in ECEF coordinate system 90 | # """ 91 | # gmst = get_gmst(jd) 92 | # rotGMST = rot_3(gmst) 93 | # ecef_pos = rotGMST.dot(eci[0:3]) 94 | # ecef_vel = rotGMST.dot(eci[3:6]) - np.cross(OMEGA_EARTH, ecef_pos) 95 | # ecef = np.concatenate((ecef_pos, ecef_vel), axis=0) 96 | 97 | # return ecef 98 | 99 | def ecef_to_pef(ecef: np.array, ttt:float, xp: float, yp: float): 100 | """ Earth-Centered, Earth-Fixed (ECEF) to Pseudo Earth-Fixed (PEF) frame. 101 | 102 | TODO: Vectorize 103 | 104 | Args: 105 | ecef (np.ndarray): ECEF coordinates 106 | jd (float): Julian day number 107 | ttt (np.ndarray): Julian centures 108 | xp: (float): x polar motion coefficient. Defaults to 0. 109 | yp: (float): y polar motion coefficient. Defaults to 0. 110 | Returns: 111 | pef (np.array): PEF coordinates 112 | """ 113 | 114 | pm = polar_motion(xp, yp, ttt) 115 | 116 | r_pef = np.dot(pm, ecef[0:3]) 117 | v_pef = np.dot(pm, ecef[3:6]) 118 | 119 | pef = np.concatenate((r_pef, v_pef)) 120 | 121 | return pef 122 | 123 | def ecef_to_teme(ecef: np.array, jd: float, t_tt: float, lod: float, xp: float, yp: float, eqeterms: int): 124 | """ Earth-Centered, Earth-Fixed (ECEF) to True Equator, Mean Equinox frame 125 | coordinate system conversion. TEME frame is used for the NORAD two-line elements. 126 | 127 | TODO: Vectorize 128 | 129 | Args: 130 | ecef (np.ndarray): ECEF coordinates 131 | t_tt (np.ndarray): Julian centures 132 | jd (float): Julian day number 133 | lod: (float): Excess length of day. Defaults to 0. 134 | xp: (float): x polar motion coefficient. Defaults to 0. 135 | yp: (float): y polar motion coefficient. Defaults to 0. 136 | eqterms (int): extra kinematic terms usage (after 1997). Options: [0, 2]. Defaults to 0. 137 | Returns: 138 | teme (np.array): TEME coordinates 139 | """ 140 | 141 | gmst = get_gmst(jd) 142 | 143 | omega = 125.04452222 + (-6962890.5390*t_tt + 7.455*t_tt**2 + 0.008*t_tt**3) / 3600.0 144 | omega = np.deg2rad(fmod(omega, 360.0)) 145 | 146 | if jd > 2450449.5 and eqeterms > 0: 147 | gmst = gmst + 0.00264*pi/(3600.0*180.0)*np.sin(omega) \ 148 | + 0.000063*pi/(3600.0*180.0)*np.sin(2.0*omega) 149 | 150 | gmst = fmod(gmst, 2.0*pi) 151 | st = rot_3(-gmst) 152 | 153 | pef = ecef_to_pef(ecef, t_tt, xp, yp) 154 | 155 | r_teme = np.dot(st, pef[0:3]) 156 | v_teme = np.dot(st, pef[3:6] + np.cross(OMEGA_EARTH, pef[0:3])) 157 | 158 | teme = np.concatenate((r_teme, v_teme)) 159 | 160 | # TODO: Add acceleration ? 161 | 162 | return teme 163 | 164 | # # ECEF to ECI 165 | # def ecef_to_eci(ecef, jd): 166 | # """ Earth-Centered, Earth-Fixed (ECEF) to Earth-Centered Inertial (ECI) 167 | # coordinate system conversion. 168 | 169 | # TODO: Vectorize 170 | 171 | # Args: 172 | # eci (np.array): Vector in ECI coordinate system (pos, velocity) 173 | # jd (float): Julian Day Number 174 | # Returns: 175 | # ecef (np.array): Vector in ECEF coordinate system 176 | # """ 177 | # rot_gmst = rot_z(get_gmst(jd)) 178 | # eci_pos = rot_gmst.dot(ecef[0:3]) 179 | # eci_vel = rot_gmst.dot(ecef[3:6] + np.cross(OMEGA_EARTH, ecef[0:3])) 180 | 181 | # return np.concatenate((eci_pos, eci_vel), axis=0) 182 | 183 | def geodetic_to_ecef(geo): 184 | """ Geodetic to Earth-Centered, Earth-Fixed coordinate System (ECEF). 185 | WGS-84 Standard (TODO: Verify). 186 | 187 | Args: 188 | geo (np.array): geodetic coordinates 189 | (latitude (rad), longitude (rad), altitude (m)) 190 | Returns: 191 | ecef (np.array): vector in ECEF coordinates 192 | """ 193 | 194 | s = np.sin(geo[0]) 195 | N = R_EQ / sqrt(1.0 - E2 * pow(s, 2)) 196 | t = (N + geo[2]) * np.cos(geo[0]) 197 | 198 | ecef = np.array([t * np.cos(geo[1]), t * np.sin(geo[1]), ((1 - E2) * N + geo[2]) * s, 0, 0, 0]) 199 | return ecef 200 | 201 | def rot_x(a): 202 | """ Rotation around x axis. 203 | 204 | Args: 205 | a (float): angle. 206 | Returns: 207 | r (np.array): rotation matrix 208 | """ 209 | 210 | s,c = np.sin(a), np.cos(a) 211 | r = np.array([[1,0,0], [0,c,-s], [0,s,c]]) 212 | return r 213 | 214 | def rot_1(a): 215 | """ Rotation around x axis. 216 | 217 | Args: 218 | a (float): angle. 219 | Returns: 220 | r (np.array): rotation matrix 221 | """ 222 | 223 | s,c = np.sin(a), np.cos(a) 224 | r = np.array([[1,0,0], [0,c,s], [0,-s,c]]) 225 | return r 226 | 227 | def rot_y(a): 228 | """ Rotation around y axis. 229 | 230 | Args: 231 | a (float): angle. 232 | Returns: 233 | r (np.array): rotation matrix 234 | """ 235 | s,c = np.sin(a), np.cos(a) 236 | r = np.array([[c,0,s], [0,1,0], [-s,0,c]]) 237 | return r 238 | 239 | def rot_2(a): 240 | """ Rotation around y axis. 241 | 242 | Args: 243 | a (float): angle. 244 | Returns: 245 | r (np.array): rotation matrix 246 | """ 247 | s,c = np.sin(a), np.cos(a) 248 | r = np.array([[c,0,-s], [0,1,0], [s,0,c]]) 249 | return r 250 | 251 | def rot_z(a): 252 | """ Rotation around z axis. 253 | 254 | Args: 255 | a (float): angle. 256 | Returns: 257 | r (np.array): rotation matrix 258 | """ 259 | s,c = np.sin(a), np.cos(a) 260 | r = np.array([[c,-s,0], [s,c,0], [0,0,1]]) 261 | return r 262 | 263 | def rot_3(a): 264 | """ Rotation around z axis. 265 | 266 | Args: 267 | a (float): angle. 268 | Returns: 269 | r (np.array): rotation matrix 270 | """ 271 | s,c = np.sin(a), np.cos(a) 272 | r = np.array([[c,s,0], [-s,c,0], [0,0,1]]) 273 | return r 274 | 275 | def polar_motion(xp:float, yp:float, ttt:float=0.0, type:str='iau-76'): 276 | """ Polar motion rotation matrix. 277 | 278 | Args: 279 | xp (float): x polar motion coefficient (rad) 280 | yp (float): y polar motion coefficient (rad) 281 | ttt (float): Julian centrues of TT, IAU-2006 282 | type (str) 283 | Returns: 284 | pm (np.array): polar motion rotation matrix 285 | """ 286 | 287 | if type=='iau-76': 288 | pm = np.dot(rot_1(yp), rot_2(xp)) 289 | elif type=='iau-2000': 290 | sp = -47.0e-6 * ttt * pi / (3600.0 * 180.0) 291 | pm = np.linalg.multi_dot([rot_3(-sp), rot_1(yp), rot_2(xp)]) 292 | 293 | return pm 294 | 295 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/example_final.py: -------------------------------------------------------------------------------- 1 | import time, argparse, os, json 2 | import numpy as np 3 | 4 | from astropy.time import Time # Astropy 4.1rc1 is used 5 | 6 | from orbitdeterminator.doppler.utils.utils import * 7 | from orbitdeterminator.doppler.utils.utils_aux import * 8 | from orbitdeterminator.doppler.utils.utils_vis import * 9 | 10 | def plot_position_norm_tdoa_batch(p_sat:np.ndarray, x_sat:np.ndarray, time:np.ndarray): 11 | """ Plot comparison between TDoA satellite position norm results and batch filter results. 12 | """ 13 | 14 | x_sat_pos_norm = np.linalg.norm(x_sat[0:3,:], axis=0) 15 | p_sat_norm = np.linalg.norm(p_sat, axis = 0) 16 | 17 | fig = plt.figure(figsize=(10,10)) 18 | fig.suptitle("Estimated satellite position vector norm ||x||") 19 | ax = fig.add_subplot(111) 20 | 21 | #ax.set_ylim([6.378e6, 7.5e6]) 22 | hg = ax.scatter(time, p_sat_norm, s=0.5) 23 | ba = ax.scatter(time[idx_start:idx_end], x_sat_pos_norm, s=0.5) 24 | ax.set_xlabel("Time (GPS seconds)") 25 | ax.set_ylabel("Sat R norm (m)") 26 | ax.grid(':') 27 | ax.legend([hg, ba], ["TDoA results", "Batch results"]) 28 | 29 | return fig 30 | 31 | def plot_batch_results_final(x_sat_result:np.ndarray, x_obs:np.ndarray): 32 | """ Plot batch results from final evaluation simulation. 33 | """ 34 | 35 | fig = plt.figure(figsize=(10,10)) 36 | ax = fig.add_subplot(111, projection='3d') 37 | 38 | font = {'size': 16} 39 | matplotlib.rc('font', **font) 40 | 41 | ax.ticklabel_format(axis="x", style="sci", scilimits=(0,0)) 42 | ax.ticklabel_format(axis="y", style="sci", scilimits=(0,0)) 43 | ax.ticklabel_format(axis="z", style="sci", scilimits=(0,0)) 44 | 45 | # Satellite 46 | ax.scatter(x_sat_result[0,:], x_sat_result[1,:], x_sat_result[2,:], s=0.5, c='k') 47 | res = ax.scatter(x_sat_result[0,0], x_sat_result[1,0], x_sat_result[2,0], c='k') # Proxy 48 | 49 | for j in range(x_obs.shape[2]): 50 | ax.scatter(x_obs[0,idx_start:idx_end,:], x_obs[1,idx_start:idx_end,:], x_obs[2,idx_start:idx_end,:], marker='.', s=0.5, c='b') 51 | 52 | obs = ax.scatter(x_obs[0,0,:], x_obs[1,0,:], x_obs[2,0,:], c='b') 53 | o = ax.scatter(0,0,0,c='teal') 54 | 55 | plot_sphere(ax, d=R_EQ, n=40) 56 | 57 | ax.set_xlabel("x ECI (m)", fontsize=16, labelpad=15) 58 | ax.set_ylabel("y ECI (m)", fontsize=16, labelpad=15) 59 | ax.set_zlabel("z ECI (m)", fontsize=16, labelpad=15) 60 | 61 | ax.legend([res, obs, o],["Result Trajectory", "Observers", "Origin"], loc=2, bbox_to_anchor=(0.15,0.9)) 62 | 63 | return fig 64 | 65 | def plot_full_orbit_final(x_b:np.ndarray): 66 | """ Plot full revolution of the final batch estimate. 67 | """ 68 | 69 | t_sec_rev = np.arange(5760) 70 | x_sat_rev = odeint(orbdyn_2body, x_b.squeeze(), t_sec_rev, args=(MU,)).T 71 | 72 | ##### Plot full orbit ##### 73 | 74 | fig = plt.figure(figsize=(10,10)) 75 | ax = fig.add_subplot(111, projection='3d') 76 | 77 | font = {'size': 16} 78 | matplotlib.rc('font', **font) 79 | 80 | ax.ticklabel_format(axis="x", style="sci", scilimits=(0,0)) 81 | ax.ticklabel_format(axis="y", style="sci", scilimits=(0,0)) 82 | ax.ticklabel_format(axis="z", style="sci", scilimits=(0,0)) 83 | 84 | ax.set_xlabel("x ECI (m)", fontsize=16, labelpad=15) 85 | ax.set_ylabel("y ECI (m)", fontsize=16, labelpad=15) 86 | ax.set_zlabel("z ECI (m)", fontsize=16, labelpad=15) 87 | 88 | ax.scatter(x_sat_rev[0,:], x_sat_rev[1,:], x_sat_rev[2,:], s=0.5, c='k') 89 | rev = ax.scatter(x_sat_rev[0,0], x_sat_rev[1,0], x_sat_rev[2,0], c='k') 90 | o = ax.scatter(0,0,0,c='teal') 91 | 92 | ax.legend([rev, o],["Result Trajectory Single Orbit", "Origin"],loc=2, bbox_to_anchor=(0.15,0.9)) 93 | 94 | plot_sphere(ax, d=R_EQ, n=40) 95 | 96 | return fig 97 | 98 | if __name__ == "__main__": 99 | 100 | # Load data 101 | data_json, data = parse_json_data("data/data_1hz.txt") 102 | n_s, n_m = data['n_s'], data['n_m'] 103 | 104 | # Conver variables 105 | data_time = data['gpstime_unix'][:,0] 106 | t_sec = data_time - data_time[0] 107 | 108 | obstime = Time(data_time, format='unix') 109 | datetime = obstime 110 | datetime.format='iso' 111 | 112 | #print(datetime) 113 | 114 | sidereal = obstime.sidereal_time('mean', data['station_pos'][1,:,0]*u.deg) 115 | x_obs = get_site_temp(data['station_pos'], obstime) 116 | 117 | data_range = data['range'].T 118 | data_range_rate = data_range[:,1:] - data_range[:,:-1] 119 | data_range_rate = np.concatenate([data_range_rate[:,0].reshape((5,1)), data_range_rate], axis=1) 120 | 121 | f_t = 1.375e8 # Hz 122 | C = 3e8 123 | data_doppler = data['doppler'].T 124 | data_doppler_range_rate = -data_doppler / f_t * C 125 | 126 | # TODO: Time 127 | print(data_range.shape) 128 | print(data_doppler_range_rate.shape) 129 | 130 | # Simulate TDoA 131 | tdoa, tof = get_tdoa_simulated_r(data['range'].T) 132 | 133 | # Solve TDoA 134 | p_sat, tau = solve_tdoa(tdoa, x_obs) 135 | 136 | # Herrick-Gibbs 137 | w = 10 # window size (assuming 1 second intervals in time array) 138 | x_sat_hg = np.zeros((6, n_m-w)) 139 | error_hg = [None] * int(n_m-w) # Error array for Herrick-Gibbs Method 140 | 141 | # Perform Herrick_Gibbs 142 | for i in range(p_sat.shape[1]-2*w): 143 | idx = np.array([i, i+w, i+2*w]) 144 | x_sat_hg[:,i], error_hg[i] = herrick_gibbs(p_sat[:, idx], t_sec[idx], angle_checks=True) 145 | 146 | if error_hg[i] is not None: 147 | print(f"Index {i}, Error {error_hg[i]}") 148 | 149 | # Batch Filter - First 150 | idx_start = 10 # 250 #300 151 | idx_end = 718 # 550 #600 152 | 153 | x_0 = np.expand_dims(x_sat_hg[:,idx_start], axis=1) # Initial estimate 154 | P = np.diag([1, 1, 1, 1, 1, 1])*1e10 # Initial uncertainty 155 | R_rr = np.eye(n_s)*1e-3 # Measurement uncertainty 156 | z_rr = -data['doppler'].T # Measurements 157 | 158 | t_sec_temp = t_sec[idx_start:idx_end] - t_sec[idx_start] 159 | 160 | x_b, output = batch( 161 | np.copy(x_0), 162 | P, 163 | R_rr, 164 | z = data_range_rate[:,idx_start:idx_end], 165 | t = t_sec_temp, 166 | x_obs=x_obs[:,idx_start:idx_end,:], 167 | f_obs=f_obs_range_rate, 168 | tolerance=1e-8, 169 | max_iterations=250 170 | ) 171 | 172 | print(output['num_it']) 173 | print(x_0.T, np.linalg.norm(x_0.T[0:3], axis=0)) 174 | print(x_b.T, np.linalg.norm(x_b.T[0:3], axis=0)) 175 | 176 | x_sat_result = odeint(orbdyn_2body, x_b.squeeze(), t_sec_temp, args=(MU,)).T 177 | 178 | # Saves 179 | output_json = {'x_sat': x_sat_result.tolist(), 'gpstime_unix': data_time[idx_start:idx_end].tolist()} 180 | 181 | with open('data/result.txt', 'w') as out_file: 182 | json.dump(output_json, out_file) 183 | 184 | ##### Plot intermediate results - 3D 185 | fig_1 = plot_tdoa_results(p_sat, x_obs, x_sat=None, angle=(35, -140)) 186 | 187 | ##### Plot position norm 188 | fig_2 = plot_position_norm_tdoa_batch(p_sat, x_sat_result, data_time) 189 | 190 | ##### Plot batch results 191 | fig_3 = plot_batch_results_final(x_sat_result, x_obs) 192 | 193 | ##### Plot full orbit 194 | fig_4 = plot_full_orbit_final(x_b) 195 | 196 | plt.show() 197 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/example_tdoa.py: -------------------------------------------------------------------------------- 1 | import os, argparse, time 2 | import numpy as np 3 | 4 | from orbitdeterminator.doppler.utils.utils import * 5 | from orbitdeterminator.doppler.utils.utils_aux import * 6 | from orbitdeterminator.doppler.utils.utils_vis import * 7 | 8 | from scipy.optimize import fsolve 9 | 10 | np.set_printoptions(precision=4) 11 | 12 | if __name__ == '__main__': 13 | 14 | # Parser 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument("--scenario_id", type=int, default=3, help="Scenario id (3). Only scenario 3 has 4 stations.") 17 | parser.add_argument("--frame", type=str, default="teme", help="Tracking frame (teme, itrf)") # Use TEME 18 | #parser.add_argument("--n_samples", type=int, default=100, help="Number of sample points") 19 | 20 | args = parser.parse_args() 21 | 22 | # TODO: Test FK5 23 | x_0, t_sec, x_sat, x_obs, _ = get_example_scenario(id=args.scenario_id, frame=args.frame) 24 | tdoa, tof = get_tdoa_simulated(x_sat, x_obs) 25 | 26 | # Solve Time Differential of Arrival multilateration 27 | p_sat, tau = solve_tdoa(tdoa, x_obs) 28 | 29 | path = "images/" # Image save path 30 | prefix = "" # Image prefix 31 | 32 | fig_1 = plot_tdoa(tdoa, tof, t_sec) 33 | fig_1.savefig(os.path.join(path, f"{prefix}_tdoa_measurements")) 34 | 35 | fig_2 = plot_tdoa_results(p_sat, x_obs, x_sat) 36 | fig_2.savefig(os.path.join(path, f"{prefix}_tdoa_results")) 37 | 38 | fig_3 = plot_tdoa_errors(p_sat, x_sat) 39 | fig_3.savefig(os.path.join(path, f"{prefix}_tdoa_errors")) 40 | 41 | # For example, take a 10-second sliding window for the simulation and apply Herrick-Gibbs method: 42 | w = 10 # window size (assuming 1 second intervals in time array) 43 | 44 | x_sat_hg = np.zeros((x_sat.shape[0], x_sat.shape[1]-w)) 45 | #t_sat_hg = t_sec[0:-w] 46 | error_hg = [None] * int(x_sat.shape[1]-w) # Error array for Herrick-Gibbs Method 47 | 48 | # Perform Herrick_Gibbs 49 | for i in range(p_sat.shape[1]-2*w): 50 | idx = np.array([i, i+w, i+2*w]) 51 | x_sat_hg[:,i], error_hg[i] = herrick_gibbs(p_sat[:, idx], t_sec[idx], angle_checks=True) 52 | 53 | if error_hg[i] is not None: 54 | print(f"Index {i}, Error {error_hg[i]}") 55 | 56 | #print(x_sat_hg.shape) 57 | 58 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/00_range_range_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/00_range_range_rate.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/00_scenario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/00_scenario.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/_tdoa_errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/_tdoa_errors.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/_tdoa_measurements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/_tdoa_measurements.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/_tdoa_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/_tdoa_results.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/batch_2_1_range_range_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/batch_2_1_range_range_rate.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/batch_2_range_range_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/batch_2_range_range_rate.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/batch_3_range_range_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/batch_3_range_range_rate.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/batch_4_range_range_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/batch_4_range_range_rate.png -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/blue_marble_resized_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/blue_marble_resized_10.jpg -------------------------------------------------------------------------------- /orbitdeterminator/doppler/images/blue_marble_resized_20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/images/blue_marble_resized_20.jpg -------------------------------------------------------------------------------- /orbitdeterminator/doppler/temp.py: -------------------------------------------------------------------------------- 1 | import os, argparse, time 2 | import numpy as np 3 | 4 | import astropy 5 | 6 | from orbitdeterminator.doppler.utils.utils import * 7 | from orbitdeterminator.doppler.utils.utils_aux import * 8 | from orbitdeterminator.doppler.utils.utils_vis import * 9 | 10 | from scipy.optimize import fsolve 11 | 12 | if __name__ == '__main__': 13 | 14 | # Parser 15 | print(astropy.__version__) 16 | print(type(astropy.__version__)) 17 | print(float(astropy.__version__[0:3])) 18 | 19 | -------------------------------------------------------------------------------- /orbitdeterminator/doppler/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/doppler/tests/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/doppler/utils/A clarification regarding the deliverabl.txt: -------------------------------------------------------------------------------- 1 | A clarification regarding the deliverables: 2 | 3 | - [Part A Analysis] Deliverables: 4 | - DC Gain 5 | - Time Constants 6 | - Simulink Diagram 7 | 8 | - [Part B Controller Design]: 9 | - -------------------------------------------------------------------------------- /orbitdeterminator/doppler/utils/constants.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | E2 = 6.69437999014e-3 # Earth eccentricity 4 | R_EQ = 6378137.0 # Equatorial radius (m) 5 | MU = 3.986004418e14 6 | C = 299792458 # Speed of light (m/s) 7 | 8 | OMEGA_EARTH = np.array([0, 0, 7.29211514670698e-05]) # Earth Rotation Rate 9 | -------------------------------------------------------------------------------- /orbitdeterminator/example_data/23908_20200316.txt: -------------------------------------------------------------------------------- 1 | 23908 96 029C 4171 E 20200316192205771 17 25 1216076+260652 37 S 2 | 23908 96 029C 4171 E 20200316192214555 17 25 1215887+244418 37 S 3 | 23908 96 029C 4171 E 20200316192224550 17 25 1215677+231385 37 S 4 | 23908 96 029C 4171 E 20200316192234570 17 25 1215522+214700 37 S 5 | 23908 96 029C 4171 E 20200316192244562 17 25 1215420+202376 37 S 6 | 23908 96 029C 4171 E 20200316192254551 17 25 1215358+190382 37 S 7 | 23908 96 029C 4171 E 20200316192304558 17 25 1215341+174670 37 S 8 | 23908 96 029C 4171 E 20200316192314562 17 25 1215359+163243 37 S 9 | 23908 96 029C 4171 E 20200316192320016 17 25 1215494+155306 37 S 10 | 23908 96 029C 4171 E 20200316210646764 17 25 0301374+433446 37 S 11 | 23908 96 029C 4171 E 20200316210656314 17 25 0310802+440905 37 S 12 | 23908 96 029C 4171 E 20200316210706315 17 25 0321301+444309 37 S 13 | 23908 96 029C 4171 E 20200316210716321 17 25 0332483+451421 37 S 14 | 23908 96 029C 4171 E 20200316210726312 17 25 0344386+454177 37 S 15 | 23908 96 029C 4171 E 20200316210732169 17 25 0351795+455594 37 S -------------------------------------------------------------------------------- /orbitdeterminator/example_data/SATOBS-ML-19200716.txt: -------------------------------------------------------------------------------- 1 | 25544 98 067A 4353 F 20160720013132250 17 25 1918175+113996 56 S-030 10 2 | 25544 98 067A 4353 F 20160720013142250 17 25 1940023+141332 56 S-030 10 3 | 25544 98 067A 4353 F 20160720013232250 17 25 2228023+262214 56 S-030 10 4 | 25544 98 067A 4353 F 20160720013322250 17 25 0118728+244644 56 S-020 10 5 | 25544 98 067A 4353 F 20160720013332250 17 25 0140828+233084 56 S-020 10 6 | 25544 98 067A 4353 F 20160720013342250 17 25 0159500+221470 56 S-015 10 7 | -------------------------------------------------------------------------------- /orbitdeterminator/example_data/iod_23908_20200316/23908_2020031619.txt: -------------------------------------------------------------------------------- 1 | 23908 96 029C 4171 E 20200316192205771 17 25 1216076+260652 37 S 2 | 23908 96 029C 4171 E 20200316192214555 17 25 1215887+244418 37 S 3 | 23908 96 029C 4171 E 20200316192224550 17 25 1215677+231385 37 S 4 | 23908 96 029C 4171 E 20200316192234570 17 25 1215522+214700 37 S 5 | 23908 96 029C 4171 E 20200316192244562 17 25 1215420+202376 37 S 6 | 23908 96 029C 4171 E 20200316192254551 17 25 1215358+190382 37 S 7 | 23908 96 029C 4171 E 20200316192304558 17 25 1215341+174670 37 S 8 | 23908 96 029C 4171 E 20200316192314562 17 25 1215359+163243 37 S 9 | 23908 96 029C 4171 E 20200316192320016 17 25 1215494+155306 37 S -------------------------------------------------------------------------------- /orbitdeterminator/example_data/iod_23908_20200316/23908_2020031621.txt: -------------------------------------------------------------------------------- 1 | 23908 96 029C 4171 E 20200316210646764 17 25 0301374+433446 37 S 2 | 23908 96 029C 4171 E 20200316210656314 17 25 0310802+440905 37 S 3 | 23908 96 029C 4171 E 20200316210706315 17 25 0321301+444309 37 S 4 | 23908 96 029C 4171 E 20200316210716321 17 25 0332483+451421 37 S 5 | 23908 96 029C 4171 E 20200316210726312 17 25 0344386+454177 37 S 6 | 23908 96 029C 4171 E 20200316210732169 17 25 0351795+455594 37 S -------------------------------------------------------------------------------- /orbitdeterminator/example_data/iod_data_af2.txt: -------------------------------------------------------------------------------- 1 | 21799 91 076C 4172 E 20180722212306446 17 25 2306031+614211 37 S 2 | 21799 91 076C 4172 E 20180722212315457 17 25 2300177+585586 37 S 3 | 21799 91 076C 4172 E 20180722212325453 17 25 2254927+555442 37 S 4 | 21799 91 076C 4172 E 20180722212605456 17 25 2230587+202634 37 S 5 | 21799 91 076C 4172 E 20180722212615458 17 25 2230384+185825 37 S 6 | 21799 91 076C 4172 E 20180722212625453 17 25 2230207+173354 37 S 7 | 21799 91 076C 4172 E 20180722212635462 17 25 2230067+161181 37 S 8 | 21799 91 076C 4172 E 20180722212645457 17 25 2230023+145399 37 S 9 | -------------------------------------------------------------------------------- /orbitdeterminator/example_data/iss_radec_generated_horizons.txt: -------------------------------------------------------------------------------- 1 | 25544 98 067A 7779 E 20180808120000000 17 25 1414084+105146 37 2 | 25544 98 067A 7779 E 20180808120020000 17 25 1417922+104686 37 3 | 25544 98 067A 7779 E 20180808120040000 17 25 1421751+104129 37 4 | 25544 98 067A 7779 E 20180808120100000 17 25 1425570+103476 37 5 | 25544 98 067A 7779 E 20180808120120000 17 25 1429376+102727 37 6 | 25544 98 067A 7779 E 20180808120140000 17 25 1433170+101884 37 7 | 25544 98 067A 7779 E 20180808120200000 17 25 1436950+100947 37 8 | 25544 98 067A 7779 E 20180808120220000 17 25 1440714+095917 37 9 | 25544 98 067A 7779 E 20180808120240000 17 25 1444460+094794 37 10 | 25544 98 067A 7779 E 20180808120300000 17 25 1448189+093580 37 11 | 25544 98 067A 7779 E 20180808120320000 17 25 1451899+092275 37 12 | 25544 98 067A 7779 E 20180808120340000 17 25 1455588+090880 37 13 | 25544 98 067A 7779 E 20180808120400000 17 25 1459256+085396 37 14 | 25544 98 067A 7779 E 20180808120420000 17 25 1502902+083825 37 15 | 25544 98 067A 7779 E 20180808120440000 17 25 1506525+082167 37 16 | 25544 98 067A 7779 E 20180808120500000 17 25 1510124+080424 37 17 | 25544 98 067A 7779 E 20180808120520000 17 25 1513697+074596 37 18 | 25544 98 067A 7779 E 20180808120540000 17 25 1517245+072686 37 19 | 25544 98 067A 7779 E 20180808120600000 17 25 1520766+070692 37 20 | 25544 98 067A 7779 E 20180808120620000 17 25 1524260+064619 37 21 | 25544 98 067A 7779 E 20180808120640000 17 25 1527726+062466 37 22 | 25544 98 067A 7779 E 20180808120700000 17 25 1531164+060234 37 23 | 25544 98 067A 7779 E 20180808120720000 17 25 1534572+053926 37 24 | 25544 98 067A 7779 E 20180808120740000 17 25 1537951+051542 37 25 | 25544 98 067A 7779 E 20180808120800000 17 25 1541300+045083 37 26 | 25544 98 067A 7779 E 20180808120820000 17 25 1544619+042552 37 27 | 25544 98 067A 7779 E 20180808120840000 17 25 1547906+035949 37 28 | 25544 98 067A 7779 E 20180808120900000 17 25 1551163+033275 37 29 | 25544 98 067A 7779 E 20180808120920000 17 25 1554388+030532 37 30 | 25544 98 067A 7779 E 20180808120940000 17 25 1557581+023722 37 31 | 25544 98 067A 7779 E 20180808121000000 17 25 1600742+020845 37 -------------------------------------------------------------------------------- /orbitdeterminator/example_data/sampleukformatdata.txt: -------------------------------------------------------------------------------- 1 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 2 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 3 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 4 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 5 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 6 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 7 | 9701201201803101520195542 01 12172038 +15585 1 5 +6 +8 190R 8 | -------------------------------------------------------------------------------- /orbitdeterminator/filters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/filters/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/filters/sav_golay.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Takes a positional data set (time, x, y, z) and applies the Savintzky Golay filter on it based on the 3 | polynomial and window parameters we input 4 | ''' 5 | 6 | from math import * 7 | import numpy as np 8 | import sys 9 | import os 10 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 11 | from scipy.signal import savgol_filter 12 | from util import read_data 13 | 14 | 15 | 16 | def golay(data, window, degree): 17 | ''' 18 | Apply the Savintzky-Golay filter to a positional data set. 19 | 20 | Args: 21 | data (numpy array): containing all of the positional data in the format of (time, x, y, z) 22 | window (int): window size of the Savintzky-Golay filter 23 | degree (int): degree of the polynomial in Savintzky-Golay filter 24 | 25 | Returns: 26 | numpy array: filtered data in the same format 27 | ''' 28 | 29 | x = data[:, 1] 30 | y = data[:, 2] 31 | z = data[:, 3] 32 | 33 | x_new = savgol_filter(x, window, degree) 34 | y_new = savgol_filter(y, window, degree) 35 | z_new = savgol_filter(z, window, degree) 36 | 37 | 38 | new_positions = np.zeros((len(data), 4)) 39 | new_positions[:, 1] = x_new 40 | new_positions[:, 2] = y_new 41 | new_positions[:, 3] = z_new 42 | new_positions[:, 0] = data[:, 0] 43 | 44 | return new_positions 45 | 46 | 47 | # 48 | # if __name__ == "__main__": 49 | # 50 | # pd.set_option('display.width', 1000) 51 | # my_data = read_data.load_data('../example_data/orbit.csv') 52 | # window = 21 # its better to select it as the len(data)/3 and it needs to be an odd number 53 | # degree = 6 54 | # positions_filtered = golay(my_data, window, degree) 55 | # print(positions_filtered - my_data) 56 | # 57 | -------------------------------------------------------------------------------- /orbitdeterminator/filters/triple_moving_average.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Here we take the average of 3 terms x0, A, B where, 3 | x0 = The point to be estimated 4 | A = weighted average of n terms previous to x0 5 | B = weighted avreage of n terms ahead of x0 6 | n = window size 7 | ''' 8 | import os 9 | import sys 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 13 | from mpl_toolkits.mplot3d import Axes3D 14 | from util import read_data as rd 15 | 16 | 17 | def weighted_average(params): 18 | ''' 19 | Calculates the weighted average of terms in the input 20 | 21 | Args: 22 | params (list): a list of numbers 23 | 24 | Returns: 25 | list: weighted average of the terms in the list 26 | ''' 27 | weighted_sum = 0 28 | weight = len(params) 29 | weight_sum = weight * (weight+1) / 2 30 | for num in params: 31 | weighted_sum += weight*num 32 | weight -= 1 33 | 34 | return weighted_sum / weight_sum 35 | 36 | 37 | def triple_moving_average(signal_array, window_size): 38 | ''' 39 | Apply triple moving average to a signal 40 | 41 | Args: 42 | signal_array (numpy array): the array of values on which the filter is to be applied 43 | window_size (int): the no. of points before and after x0 which should be considered for calculating A and B 44 | 45 | Returns: 46 | numpy array: a filtered array of size same as that of signal_array 47 | ''' 48 | filtered_signal = [] 49 | arr_len = len(signal_array) 50 | for index, point in enumerate(signal_array): 51 | if (index < window_size or index > arr_len - window_size ): 52 | filtered_signal.append(point) 53 | else: 54 | A, B = [], [] 55 | for i in range(0, window_size): 56 | A.append(signal_array[index + i]) 57 | B.append(signal_array[index - i]) 58 | 59 | wa_A = weighted_average(A) 60 | wa_B = weighted_average(B) 61 | filtered_signal.append((point + wa_B + wa_A ) / 3) 62 | 63 | return filtered_signal 64 | 65 | def generate_filtered_data(in_data, window): 66 | ''' 67 | Apply the filter and generate the filtered data 68 | 69 | Args: 70 | in_data (string): numpy array containing the positional data 71 | window (int): window size applied into the filter 72 | 73 | Returns: 74 | numpy array: the final filtered array 75 | ''' 76 | averaged_x = triple_moving_average(list(in_data[:,1]), window) 77 | averaged_y = triple_moving_average(list(in_data[:,2]), window) 78 | averaged_z = triple_moving_average(list(in_data[:,3]), window) 79 | 80 | output = np.hstack(((in_data[:,0])[:, np.newaxis], (np.array(averaged_x))[:, np.newaxis], 81 | (np.array(averaged_y))[:, np.newaxis], (np.array(averaged_z))[:, np.newaxis] )) 82 | 83 | return output 84 | 85 | if __name__ == "__main__": 86 | 87 | signal = rd.load_data(os.getcwd() + '/' + sys.argv[1]) 88 | 89 | import time 90 | time_start = time.clock() 91 | output = generate_filtered_data(signal, 2) 92 | time_stop = time.clock() 93 | 94 | print("File {} processed in {} seconds.".format( 95 | sys.argv[1], 96 | time_stop - time_start 97 | ) 98 | ) 99 | np.savetxt("filtered.csv", output, delimiter=",") 100 | 101 | print("Filtered output saved as filtered.csv") 102 | 103 | fig = plt.figure() 104 | ax = fig.add_subplot(111, projection='3d') 105 | 106 | ax.plot(output[:,1], output[:,2], output[:,3], 'b', label='filtered') 107 | ax.plot(list(signal[:,1]), list(signal[:,2]), list(signal[:,3]), 'r', label='noisy') 108 | ax.legend(['Filtered Orbit', 'Noisy Orbit']) 109 | plt.show() 110 | -------------------------------------------------------------------------------- /orbitdeterminator/filters/wiener.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Takes a positional data set (time, x, y, z) and applies the Wiener's filter on it based on the 3 | window parameters we input 4 | ''' 5 | 6 | from math import * 7 | import numpy as np 8 | import sys 9 | import os 10 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 11 | from scipy.signal import wiener 12 | from util import read_data 13 | 14 | 15 | def wiener_new(data,window): 16 | 17 | 18 | ''' 19 | Apply the Wiener's filter to a positional data set. 20 | 21 | Args: 22 | data (numpy array): containing all of the positional data in the format of (time, x, y, z) 23 | window (int): window size of the Wiener filter 24 | 25 | 26 | Returns: 27 | numpy array: filtered data in the same format 28 | ''' 29 | x=data[:,1] 30 | y=data[:,2] 31 | z=data[:,3] 32 | 33 | x_new=wiener(x,window) 34 | y_new=wiener(y,window) 35 | z_new=wiener(z,window) 36 | 37 | 38 | new_positions = np.zeros((len(data), 4)) 39 | new_positions[:, 1] = x_new 40 | new_positions[:, 2] = y_new 41 | new_positions[:, 3] = z_new 42 | new_positions[:, 0] = data[:, 0] 43 | 44 | return new_positions 45 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/kep_determination/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/gauss_example_ceres.py: -------------------------------------------------------------------------------- 1 | import gauss_method as gm 2 | 3 | # path of file of optical MPC-formatted observations 4 | filename = '../example_data/mpc_ceres_data.txt' 5 | 6 | #body name 7 | bodyname = 'Ceres' 8 | 9 | #lines of observations file to be used for orbit determination 10 | obs_arr = [7145,7146,7148,7152,7155,7156,7157,7158,7159,7164,7172,7178,7185,7190,7197,7201,7205,7213,7214,7218,7219,7221,7222,7227,7231,7240,7241,7242,7250] 11 | 12 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 13 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 14 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 15 | ###select adequate index of Gauss polynomial root 16 | # r2_root_ind_vec[4] = 1 17 | 18 | # x = a_mean, e_mean, taup_mean, I_mean, W_mean, w_mean, T_mean 19 | x = gm.gauss_method_mpc(filename, bodyname, obs_arr, refiters=10) 20 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/gauss_example_earth_sat.py: -------------------------------------------------------------------------------- 1 | import gauss_method as gm 2 | 3 | # path of file of optical IOD-formatted observations 4 | filename = '../example_data/iod_data_af2.txt' 5 | 6 | #body name 7 | # bodyname = '21799' 8 | 9 | #lines of observations file to be used for orbit determination 10 | obs_arr = [1, 3, 4, 6, 8] # LB observations of 21799 91 076C on 2018 Jul 22 11 | 12 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 13 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 14 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 15 | ###select adequate index of Gauss polynomial root 16 | # r2_root_ind_vec[4] = 1 17 | 18 | index_pass, radius_pass, velocity_pass, inclination_pass, raan_pass, eccentricity_pass, AoP_pass, \ 19 | mean_anomaly_pass, n_mean_motion_perday_pass, T_orbitperiod_pass = \ 20 | gm.gauss_method_sat(filename, obs_arr, refiters=10, plot=True) 21 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/gauss_example_eros.py: -------------------------------------------------------------------------------- 1 | import gauss_method as gm 2 | 3 | # path of file of optical MPC-formatted observations 4 | filename = '../example_data/mpc_eros_data.txt' 5 | 6 | #body name 7 | bodyname = 'Eros' 8 | 9 | #lines of observations file to be used for orbit determination 10 | obs_arr = [1, 14, 15, 24, 32, 37, 68, 81, 122, 162, 184, 206, 223] #2016 subset 11 | 12 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 13 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 14 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 15 | ###select adequate index of Gauss polynomial root 16 | # r2_root_ind_vec[4] = 1 17 | 18 | # x = a_mean, e_mean, taup_mean, I_mean, W_mean, w_mean, T_mean 19 | x = gm.gauss_method_mpc(filename, bodyname, obs_arr, refiters=10) 20 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/gauss_example_iss_jul2016.py: -------------------------------------------------------------------------------- 1 | import gauss_method as gm 2 | 3 | # path of file of optical IOD-formatted observations 4 | # body_fname_str = '../example_data/SATOBS-BY-U-073118.txt' 5 | filename = '../example_data/SATOBS-ML-19200716.txt' 6 | 7 | #body name 8 | # body_name_str = '18152 87 055A' 9 | bodyname = 'ISS (25544)' 10 | 11 | #lines of observations file to be used for orbit determination 12 | obs_arr = [1, 2, 3, 4, 5, 6] # BY observations of 18152 87 055A on 2018 Jul 31st 13 | 14 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 15 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 16 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 17 | ###select adequate index of Gauss polynomial root 18 | # r2_root_ind_vec[4] = 1 19 | 20 | obs_arr_seq = gm.gauss_method_sat_passes(filename, obs_arr, refiters=100) 21 | print(obs_arr_seq) 22 | 23 | 24 | index = [] 25 | radius = [] 26 | inclination = [] 27 | 28 | # sorting out different passes from one observation file 29 | for i in range(len(obs_arr_seq)): 30 | print() 31 | index_pass, radius_pass, velocity_pass, inclination_pass, raan_pass, eccentricity_pass, AoP_pass, \ 32 | mean_anomaly_pass, n_mean_motion_perday_pass, T_orbitperiod_pass = \ 33 | gm.gauss_method_sat(filename, obs_arr_seq[i], refiters=100, plot=False) 34 | 35 | index.append(index_pass) 36 | radius.append(radius_pass) 37 | inclination.append(inclination_pass) 38 | 39 | 40 | 41 | # checking the results. 42 | import numpy as np 43 | import matplotlib.pyplot as plt 44 | 45 | for i in range(len(inclination)): 46 | index_pass = [] 47 | inclination_pass = [] 48 | for n in range(len(inclination[i])): 49 | index_pass.append(index[i][n][0]) 50 | inclination_pass.append(inclination[i][n][0]) 51 | index_pass.append(index[i][n][1]) 52 | inclination_pass.append(inclination[i][n][1]) 53 | index_pass.append(index[i][n][2]) 54 | inclination_pass.append(inclination[i][n][2]) 55 | 56 | plt.plot(index_pass, inclination_pass, "o", label=bodyname + ' orbit pass #' + str(i)) 57 | 58 | plt.grid() 59 | plt.title('Inclination of orbit determ. (Gauss): ' + bodyname) 60 | plt.xlabel("observation point") 61 | plt.ylabel("Inclination [deg]") 62 | plt.legend() 63 | plt.show() 64 | 65 | 66 | # plotting the coordinates 67 | fig = plt.figure() 68 | ax = fig.add_subplot(111, projection='3d') 69 | 70 | # Earth-centered orbits: satellite orbit and geocenter 71 | ax.scatter3D(0.0, 0.0, 0.0, color='blue', label='Earth') 72 | 73 | for i in range(len(radius)): 74 | 75 | x_vec = [] 76 | y_vec = [] 77 | z_vec = [] 78 | 79 | for n in range(len(radius[i])): 80 | if n == n:#0: # for now, all the results are plotted 81 | x_vec.append(radius[i][n][0][0]) 82 | y_vec.append(radius[i][n][0][1]) 83 | z_vec.append(radius[i][n][0][2]) 84 | 85 | x_vec.append(radius[i][n][1][0]) 86 | y_vec.append(radius[i][n][1][1]) 87 | z_vec.append(radius[i][n][1][2]) 88 | 89 | if n == n:#len(radius) - 1: # for now, all the results are plotted 90 | x_vec.append(radius[i][n][2][0]) 91 | y_vec.append(radius[i][n][2][1]) 92 | z_vec.append(radius[i][n][2][2]) 93 | 94 | 95 | ax.scatter3D(x_vec, y_vec, z_vec, marker='+', label=bodyname + ' orbit pass #' + str(i)) 96 | 97 | plt.legend() 98 | ax.set_xlabel('x (km)') 99 | ax.set_ylabel('y (km)') 100 | ax.set_zlabel('z (km)') 101 | xy_plot_abs_max = np.max((np.amax(np.abs(ax.get_xlim())), np.amax(np.abs(ax.get_ylim())))) 102 | ax.set_xlim(-xy_plot_abs_max, xy_plot_abs_max) 103 | ax.set_ylim(-xy_plot_abs_max, xy_plot_abs_max) 104 | ax.set_zlim(-xy_plot_abs_max, xy_plot_abs_max) 105 | ax.legend() 106 | ax.set_title('Angles-only orbit determ. (Gauss): ' + bodyname) 107 | plt.show() -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/interpolation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Interpolation using splines for calculating velocity at a point and hence the orbital elements 3 | ''' 4 | import os 5 | import sys 6 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 7 | 8 | import numpy as np 9 | from scipy.interpolate import CubicSpline 10 | 11 | from util import (state_kep, read_data) 12 | 13 | 14 | def cubic_spline(orbit_data): 15 | ''' 16 | Compute component wise cubic spline of points of input data 17 | 18 | Args: 19 | orbit_data (numpy array): array of orbit data points of the 20 | format [time, x, y, z] 21 | 22 | Returns: 23 | list: component wise cubic splines of orbit data points of the format [spline_x, spline_y, spline_z] 24 | ''' 25 | time = orbit_data[:,:1] 26 | coordinates = list([orbit_data[:,1:2], orbit_data[:,2:3], orbit_data[:,3:4]]) 27 | splines = list(map(lambda a:CubicSpline(time.ravel(),a.ravel()), coordinates)) 28 | 29 | return splines 30 | 31 | def compute_velocity(spline, point): 32 | ''' 33 | Calculate the derivative of spline at the point(on the points the 34 | given spline corresponds to). This gives the velocity at that point. 35 | 36 | Args: 37 | spline (list): component wise cubic splines of orbit data points 38 | of the format [spline_x, spline_y, spline_z]. 39 | point (numpy array): point at which velocity is to be calculated. 40 | 41 | Returns: 42 | numpy array: velocity vector at the given point 43 | ''' 44 | velocity = list(map(lambda s, x:s(x, 1), spline, point)) 45 | 46 | return np.array(velocity) 47 | 48 | def main(data_points): 49 | ''' 50 | Apply the whole process of interpolation for keplerian element computation 51 | 52 | Args: 53 | data_points (numpy array): positional data set in format of (time, x, y, z) 54 | 55 | Returns: 56 | numpy array: computed keplerian elements for every point of the orbit 57 | ''' 58 | 59 | velocity_vectors = [] 60 | keplerians = [] 61 | 62 | #for index in range(len(data_points)-1): 63 | for index in range(0, min(100, len(data_points) - 1)): 64 | # Take a pair of points from data_points 65 | spline_input = data_points[index:index+2] 66 | 67 | # Calculate spline corresponding to these two points 68 | spline = cubic_spline(spline_input) 69 | 70 | # Calculate velocity corresponding 1st of the 2 points of spline_input 71 | velocity = compute_velocity(spline, spline_input[0:,1:4][0]) 72 | 73 | # Calculate keplerian elements correspong to the state vector(position, velocity) 74 | orbital_elements = state_kep.state_kep(spline_input[0:,1:4][0], velocity) 75 | 76 | velocity_vectors.append(velocity) 77 | keplerians.append(orbital_elements) 78 | 79 | # Uncomment the below statement to save the velocity vectors in a csv file. 80 | # np.savetxt('velo.csv', velocity_vectors, delimiter=",") 81 | 82 | # Take average of the keplerian elements corresponding to all the state vectors 83 | # orbit = np.array(keplerians).mean(axis=0) 84 | keplerians = np.asarray(keplerians) 85 | 86 | return keplerians 87 | 88 | # 89 | # if __name__ == "__main__": 90 | # 91 | # main() 92 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/lamberts_kalman.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Takes a positional data set and produces sets of six keplerian elements 3 | using Lambert's solution for preliminary orbit determination and Kalman filters 4 | ''' 5 | 6 | 7 | import sys 8 | import os.path 9 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 10 | from util import state_kep 11 | import numpy as np 12 | import kep_determination.lamberts_method as lm 13 | 14 | 15 | def orbit_trajectory(x1_new, x2_new, time): 16 | ''' 17 | Tool for checking if the motion of the sallite is retrogade or counter - clock wise 18 | 19 | Args: 20 | x1 (numpy array): time and position for point 1 [time1,x1,y1,z1] 21 | x2 (numpy array): time and position for point 2 [time2,x2,y2,z2] 22 | time (float): time difference between the 2 points 23 | 24 | Returns: 25 | bool: true if we want to keep retrograde, False if we want counter-clock wise 26 | ''' 27 | 28 | #l = pkp.lambert_problem(x1_new, x2_new, time, 398600.4405, False, 0) 29 | # https://esa.github.io/pykep/documentation/core.html#pykep.lambert_problem 30 | 31 | # progreade or retrograde orbit determination 32 | # in lambert_problem solver from PyKep, another baheviour is used. 33 | # there, unrealistic kepler parameters like negative semimajor axis are used to exclude the result. 34 | # in our version we now compare the behaviour in finding the better result 35 | v1_true, v2_true, ratio_true = lm.lamberts_method(x1_new, x2_new, time, True) 36 | v1_false, v2_false, ratio_false = lm.lamberts_method(x1_new, x2_new, time, False) 37 | 38 | # finding out if orbit is clockwise or counter-clockwise. 39 | # sole criterion: what has the best residual behaviour. 40 | # it looks like the right setting gets the results faster (after a few iterations n the tolerance tol is reached) 41 | traj = True 42 | if ratio_false < ratio_true: 43 | traj = False 44 | 45 | 46 | return traj 47 | 48 | 49 | def check_keplerian(kep): 50 | ''' 51 | Checks all the sets of keplerian elements to see if they have wrong values like eccentricity greater that 1 or 52 | a negative number for semi major axis 53 | 54 | Args: 55 | kep(numpy array): all the sets of keplerian elements in [semi major axis (a), eccentricity (e), 56 | inclination (i), argument of perigee (ω), right ascension of the ascending node (Ω), 57 | true anomaly (v)] format 58 | 59 | Returns: 60 | numpy array: the final corrected set of keplerian elements that will be inputed in the kalman filter 61 | ''' 62 | 63 | kep_new = list() 64 | for i in range(0, len(kep)): 65 | 66 | if kep[i, 3] < 0.0: 67 | kep[i, 3] = 360 + kep[i, 3] 68 | elif kep[i, 4] < 0.0: 69 | kep[i, 4] = 360 + kep[i, 4] 70 | 71 | if kep[i, 1] > 1.0: 72 | pass 73 | elif kep[i, 0] < 0.0: 74 | pass 75 | else: 76 | kep_new.append(kep[i, :]) 77 | 78 | kep_final = np.asarray(kep_new) 79 | 80 | return kep_final 81 | 82 | 83 | def create_kep(my_data): 84 | ''' 85 | Computes all the keplerian elements for every point of the orbit you provide using Lambert's solution 86 | It implements a tool for deleting all the points that give extremely jittery state vectors 87 | 88 | Args: 89 | data(numpy array) : contains the positional data set in (Time, x, y, z) Format 90 | 91 | 92 | Returns: 93 | numpy array: array containing all the keplerian elements computed for the orbit given in 94 | [semi major axis (a), eccentricity (e), inclination (i), argument of perigee (ω), 95 | right ascension of the ascending node (Ω), true anomaly (v)] format 96 | ''' 97 | v_hold = np.zeros((len(my_data), 3)) 98 | # v_abs1 = np.empty([len(my_data)]) 99 | 100 | x1_new = [1, 1, 1] 101 | x1_new[:] = my_data[0, 1:4] 102 | x2_new = [1, 1, 1] 103 | x2_new[:] = my_data[1, 1:4] 104 | time = my_data[1, 0] - my_data[0, 0] 105 | 106 | traj = orbit_trajectory(x1_new, x2_new, time) 107 | 108 | v1, v2, ratio = lm.lamberts_method(x1_new, x2_new, time, traj) 109 | v_hold[0] = v1 110 | 111 | # Produce all the 2 consecutive pairs and find the velocity with lamberts() method 112 | for i in range(1, (len(my_data) - 1)): 113 | 114 | j = i + 1 115 | x1_new = [1, 1, 1] 116 | x1_new[:] = my_data[i, 1:4] 117 | x2_new = [1, 1, 1] 118 | x2_new[:] = my_data[j, 1:4] 119 | time = my_data[j, 0] - my_data[i, 0] 120 | 121 | v1, v2, ratio = lm.lamberts_method(x1_new, x2_new, time, traj) 122 | v_hold[i] = v1 123 | 124 | # compute the absolute value of the velocity vector for every point 125 | # v_abs1[i] = (v1[0] ** 2 + v1[1] ** 2 + v1[2] ** 2) ** (0.5) 126 | 127 | # If the value of v_abs(i) > v_abs(0) * 10, then we dont keep that value v(i) because it is propably a bad jiitery product 128 | # if v_abs1[i] > (10 * v_abs1[0]): 129 | # v_hold[i] = v1 130 | # else: 131 | # v_hold[i] = v1 132 | 133 | # we know have lots of [0, 0, 0] inside our numpy array v(vx, vy, vz) and we dont want them because they produce a bug 134 | # when we'll try to transform these products to keplerian elements 135 | bo = list() 136 | store_i = list() 137 | for i in range(0, len(v_hold)): 138 | bo.append(np.all(v_hold[i, :] == 0.0)) 139 | 140 | for i in range(0, len(v_hold)): 141 | if bo[i] == False: 142 | store_i.append(i) 143 | 144 | # keeping only the rows with values and throwing all the [0, 0, 0] arrays 145 | final_v = np.zeros((len(store_i), 3)) 146 | j = 0 147 | for i in store_i: 148 | final_v[j] = (v_hold[i]) 149 | j += 1 150 | 151 | # collecting the position vector r(x ,y, z) that come along with the velocities kept above 152 | final_r = np.zeros((len(store_i), 3)) 153 | j = 0 154 | for i in store_i: 155 | final_r[j] = my_data[i, 1:4] 156 | j += 1 157 | 158 | # finally we transform the state vectors = position vectors + velocity vectors into keplerian elements 159 | kep = np.zeros((len(store_i), 6)) 160 | for i in range(0, len(final_r)): 161 | kep[i] = np.ravel(state_kep.state_kep(final_r[i], final_v[i])) 162 | 163 | kep = check_keplerian(kep) 164 | return kep 165 | 166 | 167 | # find the mean value of all keplerian elements set and then do a kalman filtering to find the best fit 168 | 169 | def kalman(kep, R): 170 | ''' 171 | Takes as an input lots of sets of keplerian elements and produces 172 | the fitted value of them by applying kalman filters 173 | 174 | Args: 175 | kep(numpy array): containing keplerian elements in this format (a, e, i, ω, Ω, v) 176 | R : estimate of measurement variance 177 | 178 | Returns: 179 | numpy array: final set of keplerian elements describing the orbit based on kalman filtering 180 | ''' 181 | 182 | # the mean value will be selected as the initial guess 183 | x_final = np.zeros((1, 6)) 184 | n_iter = len(kep) 185 | for i in range(0, 6): 186 | # intial parameters 187 | sz = n_iter # size of array 188 | Q = 1e-8 # process variance 189 | xhat = 0.0 # a posteri estimate of x 190 | P = 0.0 # a posteri error estimate 191 | xhatminus = 0.0 # a priori estimate of x 192 | Pminus = 0.0 # a priori error estimate 193 | K = 0.0 # gain or blending factor 194 | # intial guesses 195 | try: 196 | xhat = np.mean(kep[:, i]) 197 | except IndexError as err: 198 | print('Error: {0} \n ** Switching units to metres might help **'.format(err)) 199 | quit() 200 | 201 | P = 1.0 202 | for k in range(1, n_iter): 203 | # time update 204 | xhatminus = xhat 205 | Pminus = P + Q 206 | # measurement update 207 | K = Pminus / (Pminus + R) 208 | xhat = xhatminus + K * (kep[k, i] - xhatminus) 209 | P = (1 - K) * Pminus 210 | x_final[0, i] = xhat 211 | return x_final 212 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/lamberts_method.py: -------------------------------------------------------------------------------- 1 | """ Implements lambert's method for two topocentric radius vectors at different times. Supports both Earth-centered.""" 2 | 3 | 4 | import numpy as np 5 | from poliastro.core.stumpff import c2, c3 6 | from astropy import units as uts 7 | from astropy import constants as cts 8 | 9 | # declare astronomical constants in appropriate units 10 | mu_Earth = cts.GM_earth.to(uts.Unit('km3 / s2')).value 11 | 12 | 13 | def F_z_i(z, t, r1, r2, A): 14 | """ Function F for Newton's method 15 | 16 | :param z: 17 | :param t: 18 | :param r1: 19 | :param r2: 20 | :param A: 21 | :return: 22 | 23 | F: function 24 | 25 | """ 26 | mu = mu_Earth 27 | C_z_i = c2(z) 28 | S_z_i = c3(z) 29 | y_z = r1 + r2 + A * (z * S_z_i - 1.0) / np.sqrt(C_z_i) 30 | 31 | F = (y_z / C_z_i) ** 1.5 * S_z_i + A * np.sqrt(np.abs(y_z)) - np.sqrt(mu) * t 32 | 33 | return F 34 | 35 | 36 | def dFdz(z, r1, r2, A): 37 | """ Derivative of Function F for Netwon's Method 38 | 39 | :param z: 40 | :param r1: 41 | :param r2: 42 | :param A: 43 | :return: 44 | 45 | df: derivative for Netwon's method. 46 | 47 | """ 48 | mu = mu_Earth 49 | 50 | if z == 0: 51 | C_z_i = c2(0) 52 | S_z_i = c3(0) 53 | y_0 = r1 + r2 + A * (0 * S_z_i - 1.0) / np.sqrt(C_z_i) 54 | 55 | dF = np.sqrt(2) / 40.0 * y_0**1.5 + A / 8.0 * (np.sqrt(y_0) + A * np.sqrt(1 / 2 / y_0)) 56 | 57 | else: 58 | C_z_i = c2(z) 59 | S_z_i = c3(z) 60 | y_z = r1 + r2 + A * (z * S_z_i - 1.0) / np.sqrt(C_z_i) 61 | 62 | dF = (y_z / C_z_i)**1.5 * \ 63 | (1.0 / 2.0 / z * (C_z_i - 3.0 * S_z_i/ 2.0 / C_z_i) + 3.0 * S_z_i + 2.0 / 4.0 / C_z_i) +\ 64 | A / 8.0 * (3.0 * S_z_i / C_z_i * np.sqrt(y_z) + A * np.sqrt(C_z_i / y_z)) 65 | 66 | return dF 67 | 68 | 69 | def lamberts_method(R1, R2, delta_time, trajectory_cw=False): 70 | """ lamberts method that generates velocity vectors for each radius vectors that are put in here. 71 | 72 | Similar to https://esa.github.io/pykep/documentation/core.html#pykep.lambert_problem 73 | 74 | :param R1: radius vector of measuements #1 75 | :param R2: radius vector of measuements #2 76 | :param delta_time: delta time between R1 and R2 measurements, in seconds. Only works for same pass 77 | :param trajectory_cw: bool. True for retrograde motion (clockwise), False if counter-clock wise 78 | :return: 79 | 80 | V1: velocity vector of R1 81 | V2: velocity vector of R2 82 | ratio: absolute tolerance result 83 | 84 | """ 85 | 86 | r1 = np.linalg.norm(R1) 87 | r2 = np.linalg.norm(R2) 88 | 89 | c12 = np.cross(R1, R2) 90 | 91 | theta = np.arccos(np.dot(R1, R2) / r1 / r2) 92 | 93 | # todo: automatic check for pro or retrograde orbits is needed. currently set to prograde 94 | if trajectory_cw == False: 95 | trajectory = "prograde" 96 | if trajectory_cw == True: 97 | trajectory = "retrograde" 98 | 99 | #inclination = 0.0 100 | #if inclination >= 0.0 and inclination < 90.0: 101 | if trajectory == "prograde": 102 | if c12[2] >= 0: 103 | theta = theta 104 | else: 105 | theta = np.pi*2 - theta 106 | 107 | #if inclination >= 90.0 and inclination < 180.0: 108 | if trajectory == "retrograde": 109 | if c12[2] < 0: 110 | theta = theta 111 | else: 112 | theta = np.pi*2 - theta 113 | 114 | A = np.sin(theta) * np.sqrt(r1 * r2 / (1.0 - np.cos(theta))) 115 | 116 | z = 0.0 117 | while F_z_i(z, delta_time, r1, r2, A) < 0.0: 118 | z = z + 0.1 119 | 120 | tol = 1.e-10 # tolerance 121 | nmax = 6000 # iterations 122 | 123 | ratio = 1 124 | n = 0 125 | while (np.abs(ratio) > tol) and (n <= nmax): 126 | n = n + 1 127 | ratio = F_z_i(z, delta_time, r1, r2, A) 128 | 129 | if np.isnan(ratio) == True: 130 | break 131 | ratio = ratio / dFdz(z, r1, r2, A) 132 | z = np.abs(z - ratio) 133 | 134 | 135 | C_z_i = c2(z) 136 | S_z_i = c3(z) 137 | y_z = r1 + r2 + A * (z * S_z_i - 1.0) / np.sqrt(C_z_i) 138 | 139 | f = 1.0 - y_z / r1 140 | 141 | mu = mu_Earth 142 | g = A * np.sqrt(y_z / mu) 143 | 144 | gdot = 1.0 - y_z / r2 145 | 146 | V1 = 1.0 / g * (np.add(R2, -np.multiply(f, R1))) 147 | V2 = 1.0 / g * (np.multiply(gdot, R2) - R1) 148 | 149 | return V1, V2, np.abs(ratio) -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/radec_LS_eros.py: -------------------------------------------------------------------------------- 1 | from least_squares import gauss_LS_mpc 2 | 3 | ###path of file of optical MPC-formatted observations 4 | filename = '../example_data/mpc_eros_data.txt' 5 | 6 | ###body name 7 | bodyname = 'Eros' 8 | 9 | ###vector of line numbers in observations file to be used for preliminary orbit determination using Gauss method 10 | obs_arr = [1, 14, 15, 24, 32, 37, 68, 81, 122, 162, 184, 206, 223] #2016 observations 11 | 12 | ###vector of line numbers in observations file to be used in least squares fitting 13 | ###if obs_arr_ls is not specified, then the whole data set is used by default 14 | # obs_arr_ls = array(range(obs_arr[0], obs_arr[-1]+1)) 15 | 16 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 17 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 18 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 19 | ###select adequate index of Gauss polynomial root 20 | # r2_root_ind_vec[4] = 1 21 | 22 | x = gauss_LS_mpc(filename, bodyname, obs_arr, gaussiters=10, plot=True) 23 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/radec_LS_iss_jul2016.py: -------------------------------------------------------------------------------- 1 | # This file demonstrates the use of the Gauss method, followed by a least-squares fit, 2 | # in order to compute the orbit of an Earth-orbiting satellite, from ra/dec tracking data 3 | 4 | from least_squares import gauss_LS_sat 5 | 6 | # body name 7 | bodyname = 'ISS (25544)' 8 | 9 | # path of file of ra/dec IOD-formatted observations 10 | # the example contains tracking data for ISS (25544) 11 | filename = '../example_data/SATOBS-ML-19200716.txt' 12 | # filename = '../example_data/iss_radec_generated_horizons.txt' 13 | 14 | #lines of observations file to be used for preliminary orbit determination via Gauss method 15 | obs_arr = [2, 3, 4, 5] # ML observations of ISS on 2016 Jul 19 and 20 16 | # obs_arr = [7, 10, 13] # simulated observations generated from HORIZONS for ISS on 2018 Aug 08 17 | 18 | ###vector of line numbers in observations file to be used in least squares fitting 19 | ###if obs_arr_ls is not specified, then the whole data set is used by default 20 | # obs_arr_ls = array(range(obs_arr[0], obs_arr[-1]+1)) 21 | 22 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 23 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 24 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 25 | ###select adequate index of Gauss polynomial root 26 | # r2_root_ind_vec[4] = 1 27 | 28 | x = gauss_LS_sat(filename, bodyname, obs_arr, gaussiters=10, plot=True) 29 | -------------------------------------------------------------------------------- /orbitdeterminator/kep_determination/radec_LS_sat.py: -------------------------------------------------------------------------------- 1 | # This file demonstrates the use of the Gauss method, followed by a least-squares fit, 2 | # in order to compute the orbit of an Earth-orbiting satellite, from ra/dec tracking data 3 | 4 | from least_squares import gauss_LS_sat 5 | 6 | # body name 7 | bodyname = 'USA 74 (21799)' 8 | 9 | # path of file of ra/dec IOD-formatted observations 10 | # the example contains tracking data for satellite USA 74 11 | filename = '../example_data/iod_data_af2.txt' 12 | 13 | #lines of observations file to be used for orbit determination 14 | obs_arr = [1, 3, 4, 6, 8] # LB observations of 21799 91 076C on 2018 Jul 22 15 | 16 | #the total number of observations used 17 | # nobs = len(obs_arr) 18 | 19 | ###modify r2_root_ind_vec as necessary if adequate root of Gauss polynomial has to be selected 20 | ###if r2_root_ind_vec is not specified, then the first positive root will always be selected by default 21 | # r2_root_ind_vec = zeros((len(obs_arr)-2,), dtype=int) 22 | ###select adequate index of Gauss polynomial root 23 | # r2_root_ind_vec[4] = 1 24 | 25 | x = gauss_LS_sat(filename, bodyname, obs_arr, gaussiters=10, plot=True) -------------------------------------------------------------------------------- /orbitdeterminator/optimization/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/optimization/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/propagation/cowell.py: -------------------------------------------------------------------------------- 1 | """Numerical orbit propagator based on RK4. Takes into account J2 and drag perturbations.""" 2 | 3 | import numpy as np 4 | 5 | mu = 398600.4418 # gravitational parameter mu 6 | J2 = 1.08262668e-3 # J2 coefficient 7 | Re = 6378.137 # equatorial radius of the Earth 8 | we = 7.292115e-5 # rotation rate of the Earth in rad/s 9 | ee = 0.08181819 # eccentricity of the Earth's shape 10 | 11 | def drag(s): 12 | """Returns the drag acceleration for a given state. 13 | 14 | Args: 15 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 16 | 17 | Returns: 18 | 1x3 numpy array: the drag acceleration [ax,ay,az] 19 | """ 20 | 21 | r = np.linalg.norm(s[0:3]) 22 | v_atm = we*np.array([-s[1],s[0],0]) # calculate velocity of atmosphere 23 | v_rel = s[3:6] - v_atm 24 | 25 | rs = Re*(1-(ee*s[2]/r)**2) # calculate radius of surface 26 | h = r-rs 27 | p = 0.6*np.exp(-(h-175)*(29.4-0.012*h)/915) # in kg/km^3 28 | coeff = 3.36131e-9 # in km^2/kg 29 | acc = -p*coeff*np.linalg.norm(v_rel)*v_rel 30 | 31 | return acc 32 | 33 | def j2_pert(s): 34 | """Returns the J2 acceleration for a given state. 35 | 36 | Args: 37 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 38 | 39 | Returns: 40 | 1x3 numpy array: the J2 acceleration [ax,ay,az] 41 | """ 42 | 43 | r = np.linalg.norm(s[0:3]) 44 | K = -3*mu*J2*(Re**2)/2/r**5 45 | comp = np.array([1,1,3]) 46 | comp = comp - 5*(s[2]/r)**2 47 | comp = np.multiply(comp,s[0:3]) 48 | comp = np.multiply(K,comp) 49 | 50 | return comp 51 | 52 | def sdot(s): 53 | """Returns the time derivative of a given state. 54 | 55 | Args: 56 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 57 | 58 | Returns: 59 | 1x6 numpy array: the time derivative of s [vx,vy,vz,ax,ay,az] 60 | """ 61 | 62 | mu = 398600.4405 63 | r = np.linalg.norm(s[0:3]) 64 | a = -mu/(r**3)*s[0:3] 65 | 66 | p_j2 = j2_pert(s) 67 | p_drag = drag(s) 68 | 69 | a = a+p_j2+p_drag 70 | return np.array([*s[3:6],*a]) 71 | 72 | def rkf45(s,t0,tf,h=10,tol=1e-6): 73 | """Runge-Kutta Fehlberg 4(5) Numerical Integrator 74 | 75 | Args: 76 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 77 | t0(float) : initial time 78 | tf(float) : final time 79 | h(float) : step-size 80 | tol(float) : tolerance of error 81 | 82 | Returns: 83 | 1x6 numpy array: the state at time tf 84 | """ 85 | 86 | t = t0 87 | while(tf-t > 0.00001): 88 | if (tf-t < h): 89 | h = tf-t 90 | 91 | k1 = h*sdot(s) 92 | k2 = h*sdot(s+k1/4) 93 | k3 = h*sdot(s+3/32*k1+9/32*k2) 94 | k4 = h*sdot(s+1932/2197*k1-7200/2197*k2+7296/2197*k3) 95 | k5 = h*sdot(s+439/216*k1-8*k2+3680/513*k3-845/4104*k4) 96 | k6 = h*sdot(s-8/27*k1+2*k2-3544/2565*k3+1859/4104*k4-11/40*k5) 97 | 98 | y = s+25/216*k1+1408/2565*k3+2197/4104*k4-k5/5 99 | z = s+16/135*k1+6656/12825*k3+28561/56430*k4-9/50*k5+2/55*k6 100 | 101 | s = y 102 | t = t+h 103 | 104 | err = np.linalg.norm(y-z) 105 | h = h*0.84*(tol/err)**0.25 106 | 107 | return s 108 | 109 | def rk4(s,t0,tf,h=30): 110 | """Runge-Kutta 4th Order Numerical Integrator 111 | 112 | Args: 113 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 114 | t0(float) : initial time 115 | tf(float) : final time 116 | h(float) : step-size 117 | 118 | Returns: 119 | 1x6 numpy array: the state at time tf 120 | """ 121 | 122 | t = t0 123 | 124 | if tf < t0: 125 | h = -h 126 | 127 | while(abs(tf-t) > 0.00001): 128 | if (abs(tf-t) < abs(h)): 129 | h = tf-t 130 | 131 | k1 = h*sdot(s) 132 | k2 = h*sdot(s+k1/2) 133 | k3 = h*sdot(s+k2/2) 134 | k4 = h*sdot(s+k3) 135 | 136 | s = s+(k1+2*k2+2*k3+k4)/6 137 | t = t+h 138 | 139 | # if (s[2]<0 and s[2]>-200 and s[5]>0): 140 | # dt = -s[2]/s[5] 141 | # print(t+dt) 142 | 143 | return s 144 | 145 | def time_period(s,h=30): 146 | """Returns the nodal time period of an orbit. 147 | 148 | Args: 149 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 150 | h(float): step-size 151 | 152 | Returns: 153 | float: the nodal time period of the orbit 154 | """ 155 | 156 | t = 0 157 | 158 | old_z, pass_1 = 0, None 159 | 160 | while(True): 161 | k1 = h*sdot(s) 162 | k2 = h*sdot(s+k1/2) 163 | k3 = h*sdot(s+k2/2) 164 | k4 = h*sdot(s+k3) 165 | 166 | s = s+(k1+2*k2+2*k3+k4)/6 167 | t = t+h 168 | 169 | if (s[2]>=0 and old_z<0): 170 | dt = -s[2]/s[5] 171 | t2 = t+dt 172 | 173 | if pass_1 is None: 174 | pass_1 = t2 175 | else: 176 | return t2-pass_1 177 | 178 | old_z = s[2] 179 | 180 | def propagate_state(s,t0,tf): 181 | """Equivalent to the rk4 function.""" 182 | 183 | return rk4(s,t0,tf) 184 | 185 | if __name__ == "__main__": 186 | s = np.array([2.87393871e+03,5.22992358e+03,3.23958865e+03,-3.49496655e+00,4.87211332e+00,-4.76792145e+00]) 187 | t0, tf = 0, 88796.3088704 188 | final = propagate_state(s,t0,tf) 189 | print(final) 190 | -------------------------------------------------------------------------------- /orbitdeterminator/propagation/dgsn_simulator.py: -------------------------------------------------------------------------------- 1 | """Simulates signals from the DGSN. This is like simulator.py but 2 | gives noisy data. Simulated effects are unequal intervals between 3 | observations, Gaussian noise in observed coordinates, and gaps in 4 | between observations. The gaps simulate the time when the station 5 | will be out of range of the satellite. 6 | """ 7 | 8 | import time 9 | import random 10 | import signal 11 | import threading 12 | from functools import partial 13 | import numpy as np 14 | 15 | from cowell import propagate_state 16 | import sys 17 | import os 18 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 19 | from util.teme_to_ecef import conv_to_ecef 20 | from util.new_tle_kep_state import kep_to_state 21 | 22 | class DGSNSimulator(): 23 | """A class for the simulator.""" 24 | 25 | def __init__(self,params): 26 | """Initializes the simulator. 27 | 28 | Args: 29 | params: A SimParams object containing kep,t0,t,period,speed, 30 | op_writer, dgsn_period, and dgsn_thresh. For a 31 | description of the parameters, look at the documentation 32 | of the SimParams class. 33 | 34 | Returns: 35 | nothing 36 | """ 37 | 38 | self.s = kep_to_state(params.kep).flatten() 39 | self.t0 = params.epoch 40 | self.t = params.t0-params.period 41 | self.period = params.period 42 | self.speed = params.speed 43 | self.op_writer = params.op_writer 44 | 45 | self.s = propagate_state(self.s,self.t0,self.t) 46 | self.t0 = self.t 47 | 48 | if params.dgsn_period is not None and params.dgsn_period > 0: 49 | self.dgsn_omega = np.pi/params.dgsn_period 50 | self.dgsn_thresh = params.dgsn_thresh 51 | else: 52 | self.dgsn_omega = None 53 | 54 | self.r_jit = params.r_jit 55 | self.dgsn_period = params.dgsn_period 56 | 57 | self.calc_thr = None 58 | self.is_running = False 59 | 60 | def simulate(self): 61 | """Starts the calculation thread and waits for keyboard input. 62 | Press q or Ctrl-C to quit the simulator cleanly.""" 63 | 64 | self.is_running = True 65 | 66 | self.op_writer.open() 67 | self.calc_thr = threading.Timer(0, self.calc) 68 | self.calc_thr.start() 69 | 70 | # listen for commands. 71 | # only quit command implemented for now 72 | while self.is_running: 73 | c = input() 74 | if (c == 'q'): 75 | self.stop() 76 | 77 | def calc(self): 78 | """Calculates the satellite state at current time and 79 | calls itself after a certain amount of time.""" 80 | 81 | interval = random.randint(1,self.period) 82 | calc_period = max(0,interval/self.speed) 83 | self.calc_thr = threading.Timer(calc_period, self.calc) 84 | self.calc_thr.start() 85 | self.t += interval 86 | self.s = propagate_state(self.s,self.t0,self.t) 87 | self.t0 = self.t 88 | r = self.s[0:3] 89 | 90 | r[0] += random.gauss(0,self.r_jit) 91 | r[1] += random.gauss(0,self.r_jit) 92 | r[2] += random.gauss(0,self.r_jit) 93 | #r[0] += random.uniform(-self.r_jit,self.r_jit) 94 | #r[1] += random.uniform(-self.r_jit,self.r_jit) 95 | #r[2] += random.uniform(-self.r_jit,self.r_jit) 96 | 97 | if self.dgsn_omega is not None: 98 | prob = abs(np.cos(self.dgsn_omega*self.t)) 99 | 100 | if (prob >= self.dgsn_thresh): 101 | self.op_writer.write(self.t,r) 102 | else: 103 | self.op_writer.write(self.t0,r) 104 | 105 | def stop(self): 106 | """Stops the simulator cleanly.""" 107 | 108 | if self.calc_thr is not None: 109 | self.calc_thr.cancel() 110 | self.is_running = False 111 | self.op_writer.close() 112 | 113 | def __sig_handler(simulator, signal, frame): 114 | """Ctrl-C handler""" 115 | 116 | simulator.stop() 117 | sys.exit(0) 118 | 119 | class OpWriter(): 120 | """Base output writer class. Inherit this class 121 | and override the methods.""" 122 | 123 | def open(self): 124 | """Anything that has to be executed before 125 | starting to write output. Runs once. 126 | 127 | Example: Establishing connection to database 128 | """ 129 | 130 | pass 131 | 132 | @staticmethod 133 | def write(t,r): 134 | """This method is called everytime the calc thread 135 | finishes a computation. 136 | 137 | Args: 138 | t: the current time of simulation 139 | s: the state vector at t [rx,ry,rz,vx,vy,vz] 140 | """ 141 | 142 | print(t,*r) 143 | 144 | def close(self): 145 | """Anything that has to be executed after 146 | finishing writing the output. Runs once. 147 | 148 | Example: Closing connection to a database 149 | """ 150 | 151 | pass 152 | 153 | class print_r(OpWriter): 154 | """Prints the position vector""" 155 | pass 156 | 157 | class print_lat_lon(OpWriter): 158 | """Prints the latitude and longitude""" 159 | @staticmethod 160 | def write(t,r): 161 | t,lat,lon,alt = conv_to_ecef(np.array([[t,*r]]))[0] 162 | print("{} {} {} {}".format(int(t),lat,lon,alt)) 163 | 164 | class save_r(OpWriter): 165 | """Saves the position vector to a file""" 166 | def __init__(self, name): 167 | """Initialize the class. 168 | 169 | Args: 170 | name(string): file name 171 | """ 172 | 173 | self.file_name = name 174 | self.iter = 0 175 | 176 | def open(self): 177 | #self.f = open(self.file_name,'a+') 178 | self.t = None 179 | 180 | def write(self,t,r): 181 | if not self.t == t: 182 | self.f = open(self.file_name,'a+') 183 | self.f.write("{} {} {} {}\r\n".format(t,*r)) 184 | self.f.close() 185 | self.t = t 186 | print("\rIteration:",self.iter,end=' '*10) 187 | self.iter+=1 188 | 189 | def close(self): 190 | #self.f.close() 191 | pass 192 | 193 | class SimParams(): 194 | """SimParams class. This is just a container for all 195 | the parameters required to start the simulation. 196 | 197 | Params 198 | ------ 199 | kep(1x6 numpy array): the intial osculating keplerian elements 200 | epoch(float): the epoch of the above kep 201 | period(float): maximum time period between observations 202 | t0(float): starting time of the simulation 203 | speed(float): speed of the simulation 204 | op_writer(OpWriter): output handling object 205 | 206 | r_jit(float): std of Gaussian noise applied to observations 207 | dgsn_period(float): average time period between gaps 208 | dgsn_thresh(float): used to control the duration of the gap. 209 | it is a number between 0 and 1. a higher 210 | number means a bigger gap. 211 | """ 212 | 213 | kep = None 214 | epoch = None 215 | period = 1 216 | t0 = int(time.time()) 217 | speed = 1 218 | op_writer = print_r() 219 | 220 | r_jit = 0 221 | dgsn_period = None 222 | dgsn_thresh = 0.5 223 | 224 | if __name__ == "__main__": 225 | epoch = 1531152114 226 | #epoch = 1530729961 227 | #epoch = 1529410874 228 | #iss_kep = np.array([6775,0.0002893,51.6382,211.1340,7.1114,148.9642]) 229 | 230 | iss_kep = np.array([6785.6420,0.0003456,51.6418,290.0933,266.6543,212.4306]) 231 | params = SimParams() 232 | params.kep = iss_kep 233 | params.epoch = epoch 234 | params.period = 1 235 | params.speed = 10 236 | 237 | params.r_jit = 15 238 | #params.dgsn_period = 1350 239 | #params.dgsn_thresh = 0.7 240 | 241 | params.op_writer = save_r('ISS_DGSN.csv') 242 | 243 | s = DGSNSimulator(params) 244 | signal.signal(signal.SIGINT, partial(__sig_handler,s)) 245 | s.simulate() 246 | -------------------------------------------------------------------------------- /orbitdeterminator/propagation/kalman_filter.py: -------------------------------------------------------------------------------- 1 | """Kalman Filter to smoothen observations. It continuously reads a file 2 | where observations are being written and updates its estimate based 3 | on the observations and the cowell model.""" 4 | 5 | import time 6 | import numpy as np 7 | from functools import partial 8 | from cowell import rk4 9 | 10 | class KalmanFilter(): 11 | """Kalman Filter class wrapper.""" 12 | 13 | @staticmethod 14 | def __Jacobian(s,t0,tf): 15 | """Numerically computes the Jacobian of rk4(s,t0,tf). 16 | 17 | Args: 18 | s(1x6 numpy array): the state vector at t0 [rx,ry,rz,vx,vy,vz] 19 | t0(float): the intial time 20 | tf(float): the final time 21 | 22 | Returns: 23 | 3x3 numpy matrix: the topleft half of the Jacobian of rk4(s,t0,tf) 24 | """ 25 | 26 | f = partial(rk4,t0=t0,tf=tf) 27 | F = np.empty((3,3)) 28 | a = np.zeros(6) 29 | 30 | h = 0.0005 31 | a[0] = h 32 | F[:,0] = (f(s+a) - f(s-a))[0:3]/2/h 33 | 34 | a[0], a[1] = 0, h 35 | F[:,1] = (f(s+a) - f(s-a))[0:3]/2/h 36 | 37 | a[1], a[2] = 0, h 38 | F[:,2] = (f(s+a) - f(s-a))[0:3]/2/h 39 | 40 | return F 41 | 42 | def process(self,s,t0,dgsn_file): 43 | """The main Kalman Filter. Continuously reads an obervations file and 44 | updates the state estimate. 45 | 46 | Args: 47 | s(1x6 numpy array): the state vector [rx,ry,rz,vx,vy,vz] 48 | t0(float): epoch of s 49 | dgsn_file(string): path to the observations file 50 | 51 | Returns: 52 | nothing 53 | """ 54 | 55 | self.s = s 56 | self.t0 = t0 57 | self.P = np.diag([900,900,900]) # prediction error 58 | self.Q = np.diag([100,100,100]) # model error 59 | self.R = np.diag([900,900,900]) # obs error 60 | 61 | f = open(dgsn_file,'r') 62 | f.seek(0,2) 63 | while True: 64 | line = f.readline() 65 | if not line: 66 | time.sleep(0.1) 67 | continue 68 | 69 | state = line.split() 70 | if state[0] == '#': 71 | continue 72 | 73 | t = int(state[0]) 74 | z = [float(x) for x in state[1:4]] 75 | 76 | # predict 77 | self.s = rk4(self.s, self.t0, t) 78 | 79 | #if (t-self.t0 > 100): 80 | # self.s[0:3] = z 81 | # self.t0 = t 82 | # continue 83 | 84 | F = self.__Jacobian(self.s, self.t0, t) 85 | 86 | self.P = np.matmul(self.P,F.T) 87 | self.P = np.matmul(F,self.P) 88 | self.P = self.P + self.Q 89 | 90 | # update 91 | y = z - self.s[0:3] 92 | S_inv = np.linalg.inv(self.P + self.R) 93 | K = np.matmul(self.P, S_inv) 94 | self.s[0:3] = self.s[0:3] + np.matmul(K,y) 95 | self.P = np.matmul((np.eye(3) - K),self.P) 96 | 97 | self.t0 = t 98 | print(t,z[0],self.s[0]) 99 | 100 | 101 | if __name__ == '__main__': 102 | s = np.array([2.87327861e+03,5.22872234e+03,3.23884457e+03,-3.49536799e+00,4.87267295e+00,-4.76846910e+00]) 103 | t0 = 1531152114 104 | 105 | KalmanFilter().process(s,t0,'ISS_DGSN.csv') 106 | -------------------------------------------------------------------------------- /orbitdeterminator/propagation/sgp4_prop.py: -------------------------------------------------------------------------------- 1 | """SGP4 propagator. This is a wrapper around the PyPI SGP4 propagator. 2 | However, this does not generate an artificial TLE. So there is no 3 | string manipulation involved. Hence this is faster than sgp4_prop_string.""" 4 | 5 | from datetime import datetime 6 | import numpy as np 7 | from sgp4.model import Satellite 8 | from sgp4.earth_gravity import wgs72 9 | from sgp4.propagation import sgp4init 10 | import sys 11 | import os 12 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 13 | from util.state_kep import state_kep 14 | 15 | def __true_to_mean(T,e): 16 | """Converts true anomaly to mean anomaly. 17 | 18 | Args: 19 | T(float): true anomaly in degrees 20 | e(float): eccentricity 21 | 22 | Returns: 23 | float: the mean anomaly in degrees 24 | """ 25 | 26 | T = np.radians(T) 27 | E = np.arctan2((1-e**2)*np.sin(T),e+np.cos(T)) 28 | M = E - e*np.sin(E) 29 | M = np.degrees(M) 30 | M = M%360 31 | return M 32 | 33 | # Parts of this method have been copied from: 34 | # https://github.com/brandon-rhodes/python-sgp4/blob/master/sgp4/io.py 35 | def kep_to_sat(kep,epoch,bstar=0.21109E-4,whichconst=wgs72,afspc_mode=False): 36 | """kep_to_sat(kep,epoch,bstar=0.21109E-4,whichconst=wgs72,afspc_mode=False) 37 | 38 | Converts a set of keplerian elements into a Satellite object. 39 | 40 | Args: 41 | kep(1x6 numpy array): the osculating keplerian elements at epoch 42 | epoch(float): the epoch 43 | bstar(float): bstar drag coefficient 44 | whichconst(float): gravity model. refer pypi sgp4 documentation 45 | afspc_mode(boolean): refer pypi sgp4 documentation 46 | 47 | Returns: 48 | Satellite object: an sgp4 satellite object encapsulating the arguments 49 | """ 50 | 51 | deg2rad = np.pi / 180.0; # 0.0174532925199433 52 | xpdotp = 1440.0 / (2.0 * np.pi); # 229.1831180523293 53 | 54 | tumin = whichconst.tumin 55 | 56 | satrec = Satellite() 57 | satrec.error = 0; 58 | satrec.whichconst = whichconst # Python extension: remembers its consts 59 | 60 | satrec.satnum = 0 61 | dt_obj = datetime.utcfromtimestamp(epoch) 62 | t_obj = dt_obj.timetuple() 63 | satrec.epochdays = (t_obj.tm_yday + 64 | t_obj.tm_hour/24 + 65 | t_obj.tm_min/1440 + 66 | t_obj.tm_sec/86400) 67 | satrec.ndot = 0 68 | satrec.nddot = 0 69 | satrec.bstar = bstar 70 | 71 | satrec.inclo = kep[2] 72 | satrec.nodeo = kep[4] 73 | satrec.ecco = kep[1] 74 | satrec.argpo = kep[3] 75 | satrec.mo = __true_to_mean(kep[5],kep[1]) 76 | satrec.no = 86400/(2*np.pi*(kep[0]**3/398600.4405)**0.5) 77 | 78 | satrec.no = satrec.no / xpdotp; # rad/min 79 | satrec.a = pow( satrec.no*tumin , (-2.0/3.0) ); 80 | 81 | # ---- find standard orbital elements ---- 82 | satrec.inclo = satrec.inclo * deg2rad; 83 | satrec.nodeo = satrec.nodeo * deg2rad; 84 | satrec.argpo = satrec.argpo * deg2rad; 85 | satrec.mo = satrec.mo * deg2rad; 86 | 87 | satrec.alta = satrec.a*(1.0 + satrec.ecco) - 1.0; 88 | satrec.altp = satrec.a*(1.0 - satrec.ecco) - 1.0; 89 | 90 | satrec.epochyr = dt_obj.year 91 | satrec.jdsatepoch = epoch/86400.0 + 2440587.5 92 | satrec.epoch = dt_obj 93 | 94 | # ---------------- initialize the orbit at sgp4epoch ------------------- 95 | sgp4init(whichconst, afspc_mode, satrec.satnum, satrec.jdsatepoch-2433281.5, satrec.bstar, 96 | satrec.ecco, satrec.argpo, satrec.inclo, satrec.mo, satrec.no, 97 | satrec.nodeo, satrec) 98 | 99 | return satrec 100 | 101 | def propagate_kep(kep,t0,tf,bstar=0.21109E-4): 102 | """Propagates a set of keplerian elements. 103 | 104 | Args: 105 | kep(1x6 numpy array): osculating keplerian elements at epoch 106 | t0(float): initial time (epoch) 107 | tf(float): final time 108 | 109 | Returns: 110 | pos(1x3 numpy array): the position at tf 111 | vel(1x3 numpy array): the velocity at tf 112 | """ 113 | 114 | sat = kep_to_sat(kep,t0,bstar=bstar) 115 | tf = datetime.utcfromtimestamp(tf).timetuple() 116 | pos, vel = sat.propagate( 117 | tf.tm_year, tf.tm_mon, tf.tm_mday, tf.tm_hour, tf.tm_min, tf.tm_sec) 118 | 119 | return np.array(list(pos)),np.array(list(vel)) 120 | 121 | def propagate_state(r,v,t0,tf,bstar=0.21109E-4): 122 | """Propagates a state vector 123 | 124 | Args: 125 | r(1x3 numpy array): the position vector at epoch 126 | v(1x3 numpy array): the velocity vector at epoch 127 | t0(float): initial time (epoch) 128 | tf(float): final time 129 | 130 | Returns: 131 | pos(1x3 numpy array): the position at tf 132 | vel(1x3 numpy array): the velocity at tf 133 | """ 134 | 135 | kep = state_kep(r,v) 136 | return propagate_kep(kep,t0,tf,bstar) 137 | 138 | if __name__ == "__main__": 139 | 140 | t0 = 1526927274 141 | tf = 1526932833 142 | 143 | #kep = np.array([6782.96, 0.0004084, 51.6402, 108.2140, 150.4026, 238.0528]) 144 | 145 | r = np.array([-5.23684633e+03, 4.12417773e+03, -1.26294137e+03]) 146 | v = np.array([-3.86204515e+00, -3.12048032e+00, 5.83839029e+00]) 147 | 148 | #pos,vel = propagate_kep(kep,t0,tf) 149 | pos,vel = propagate_state(r,v,t0,tf) 150 | 151 | print(pos,vel) 152 | -------------------------------------------------------------------------------- /orbitdeterminator/propagation/sgp4_prop_string.py: -------------------------------------------------------------------------------- 1 | """SGP4 propagator. This is a wrapper around PyPI SGP4 propagator. 2 | It constructs an artificial TLE and passes it to the PyPI module.""" 3 | 4 | import time 5 | 6 | import numpy as np 7 | 8 | from sgp4.earth_gravity import wgs72 9 | from sgp4.io import twoline2rv 10 | 11 | avg_bstar = 0.21109E-4 12 | 13 | def propagate(kep,init_time,final_time,bstar=avg_bstar): 14 | """Propagates a set of keplerian elements. 15 | 16 | Args: 17 | kep(1x6 numpy array): osculating keplerian elements at epoch 18 | init_time(float): initial time (epoch) 19 | final_time(float): final time 20 | bstar(float): bstar drag coefficient 21 | 22 | Returns: 23 | pos(1x3 numpy array): the position at tf 24 | vel(1x3 numpy array): the velocity at tf 25 | """ 26 | 27 | t0 = time.gmtime(init_time) 28 | 29 | t0 = ((t0.tm_year%100)*1000 + 30 | t0.tm_yday+ 31 | t0.tm_hour/24 + t0.tm_min/1440 + t0.tm_sec/86400) 32 | 33 | t0 = "{:14.8f}".format(t0) 34 | tf = time.gmtime(final_time) 35 | 36 | mu = 398600.4405 37 | n = 86400/2/np.pi * (mu/kep[0]**3)**0.5 38 | 39 | tanom = np.radians(kep[5]) 40 | e = kep[1] 41 | ecc = np.arctan2((1-e**2)**0.5*np.sin(tanom),e+np.cos(tanom)) 42 | ecc = ecc%(2*np.pi) 43 | mean = ecc - e*np.sin(ecc) 44 | mean = np.degrees(mean) 45 | 46 | inc = "{:8.4f}".format(kep[2]) 47 | raan = "{:8.4f}".format(kep[4]) 48 | e = "{:.7f}".format(e)[2:] 49 | argp = "{:8.4f}".format(kep[3]) 50 | mean = "{:8.4f}".format(mean) 51 | n = "{:11.8f}".format(n) 52 | 53 | bexp = np.floor(np.log10(abs(bstar)))+1 54 | bstar = "{:+5}{:+.0f}".format(int(bstar*10**(-bexp+5)),bexp) 55 | 56 | line1 = ('1 00000U 000000 '+t0+' ' 57 | '.00000000 00000-0 '+bstar+' 0 0000') 58 | line2 = ('2 00000 '+inc+' '+raan+' '+e+' '+argp+ 59 | ' '+mean+' '+n+'000000') 60 | 61 | satellite = twoline2rv(line1, line2, wgs72) 62 | position, velocity = satellite.propagate( 63 | tf.tm_year, tf.tm_mon, tf.tm_mday, tf.tm_hour, tf.tm_min, tf.tm_sec) 64 | 65 | return position,velocity 66 | 67 | if __name__ == "__main__": 68 | t0 = 1526927274 69 | tf = 1526932833 70 | 71 | kep = np.array([6782.96, 0.0004084, 51.6402, 108.2140, 150.4026, 238.0528]) 72 | 73 | pos, vel = propagate(kep,t0,tf) 74 | print(pos) 75 | print(vel) 76 | -------------------------------------------------------------------------------- /orbitdeterminator/propagation/simulator.py: -------------------------------------------------------------------------------- 1 | """Outputs a satellite's location periodically.""" 2 | 3 | import sys 4 | import time 5 | import signal 6 | import threading 7 | from functools import partial 8 | import numpy as np 9 | import sys 10 | import os 11 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 12 | from propagation.cowell import propagate_state 13 | from util.teme_to_ecef import conv_to_ecef 14 | from util.new_tle_kep_state import kep_to_state 15 | 16 | class Simulator(): 17 | """A class for the simulator.""" 18 | 19 | def __init__(self,params): 20 | """Initializes the simulator. 21 | 22 | Args: 23 | params: A SimParams object containing kep,t0,t,period,speed, 24 | and op_writer 25 | 26 | Returns: 27 | nothing 28 | """ 29 | 30 | self.s = kep_to_state(params.kep).flatten() 31 | self.t0 = params.epoch 32 | self.t = params.t0-params.period 33 | self.period = params.period 34 | self.speed = params.speed 35 | self.op_writer = params.op_writer 36 | 37 | self.s = propagate_state(self.s,self.t0,self.t) 38 | self.t0 = self.t 39 | 40 | self.calc_thr = None 41 | self.is_running = False 42 | 43 | def simulate(self): 44 | """Starts the calculation thread and waits for keyboard input. 45 | Press q or Ctrl-C to quit the simulator cleanly.""" 46 | 47 | self.is_running = True 48 | 49 | self.op_writer.open() 50 | self.calc_thr = threading.Timer(0, self.calc) 51 | self.calc_thr.start() 52 | 53 | # listen for commands. 54 | # only quit command implemented for now 55 | while self.is_running: 56 | c = input() 57 | if (c == 'q'): 58 | self.stop() 59 | 60 | def calc(self): 61 | """Calculates the satellite state at current time and 62 | calls itself after a certain amount of time.""" 63 | 64 | calc_period = max(0,self.period/self.speed) 65 | self.calc_thr = threading.Timer(calc_period, self.calc) 66 | self.calc_thr.start() 67 | self.t += self.period 68 | self.s = propagate_state(self.s,self.t0,self.t) 69 | self.t0 = self.t 70 | self.op_writer.write(self.t,self.s) 71 | 72 | def stop(self): 73 | """Stops the simulator cleanly.""" 74 | 75 | if self.calc_thr is not None: 76 | self.calc_thr.cancel() 77 | self.is_running = False 78 | self.op_writer.close() 79 | 80 | def __sig_handler(simulator, signal, frame): 81 | """Ctrl-C handler""" 82 | 83 | simulator.stop() 84 | sys.exit(0) 85 | 86 | class OpWriter(): 87 | """Base output writer class. Inherit this class 88 | and override the methods.""" 89 | 90 | def open(self): 91 | """Anything that has to be executed before 92 | starting to write output. Runs once. 93 | 94 | Example: Establishing connection to database 95 | """ 96 | 97 | pass 98 | 99 | @staticmethod 100 | def write(t,s): 101 | """This method is called everytime the calc thread 102 | finishes a computation. 103 | 104 | Args: 105 | t: the current time of simulation 106 | s: the state vector at t [rx,ry,rz,vx,vy,vz] 107 | """ 108 | 109 | print(t,*s) 110 | 111 | def close(self): 112 | """Anything that has to be executed after 113 | finishing writing the output. Runs once. 114 | 115 | Example: Closing connection to a database 116 | """ 117 | 118 | pass 119 | 120 | # Sample op_writer classes 121 | class print_r(OpWriter): 122 | """Prints the position vector""" 123 | @staticmethod 124 | def write(t,s): 125 | print(t,*s[0:3]) 126 | 127 | class print_lat_lon(OpWriter): 128 | """Prints the latitude and longitude""" 129 | @staticmethod 130 | def write(t,s): 131 | t,lat,lon,alt = conv_to_ecef(np.array([[t,*s[0:3]]]))[0] 132 | print(t,lat,lon,alt) 133 | 134 | class save_r(OpWriter): 135 | """Saves the position vector to a file""" 136 | def __init__(self, name): 137 | """Initialize the class. 138 | 139 | Args: 140 | name(string): file name 141 | """ 142 | 143 | self.file_name = name 144 | 145 | def open(self): 146 | #self.f = open(self.file_name,'a') 147 | self.t = None 148 | self.iter = 0 149 | self.f = open(self.file_name,'a') 150 | self.f.write('# Begin write\r\n') 151 | self.f.close() 152 | 153 | def write(self,t,s): 154 | if not self.t == t: 155 | self.f = open(self.file_name,'a') 156 | self.f.write("{} {} {} {}\r\n".format(t,*s)) 157 | self.f.close() 158 | self.t = t 159 | print("\rIteration:",self.iter,end=' '*10) 160 | self.iter+=1 161 | 162 | def close(self): 163 | self.f.close() 164 | 165 | class SimParams(): 166 | """SimParams class. This is just a container for all 167 | the parameters required to start the simulation. 168 | 169 | Params 170 | ------ 171 | kep(1x6 numpy array): the intial osculating keplerian elements 172 | epoch(float): the epoch of the above kep 173 | period(float): maximum time period between observations 174 | t0(float): starting time of the simulation 175 | speed(float): speed of the simulation 176 | op_writer(OpWriter): output handling object 177 | 178 | """ 179 | 180 | kep = None 181 | epoch = None 182 | period = 1 183 | t0 = int(time.time()) 184 | speed = 1 185 | op_writer = OpWriter() 186 | 187 | if __name__ == "__main__": 188 | epoch = 1531152114 189 | iss_kep = np.array([6785.68682,0.0003456,51.6418,290.0933,266.6543,212.430557]) 190 | 191 | params = SimParams() 192 | params.kep = iss_kep 193 | params.epoch = epoch 194 | params.op_writer = print_lat_lon() #save_r('ISS.csv') 195 | 196 | s = Simulator(params) 197 | signal.signal(signal.SIGINT, partial(__sig_handler,s)) 198 | s.simulate() 199 | -------------------------------------------------------------------------------- /orbitdeterminator/station_observatory_data/sat_tracking_observatories.txt: -------------------------------------------------------------------------------- 1 | # File was retrieved from sattools/data/sites.txt (GitHub repo) 2 | No ID Latitude Longitude Elev Observer 3 | 1111 RL 38.9478 -104.5614 2073 Ron Lee 4 | 4171 CB 52.8344 6.3785 10 Cees Bassa 5 | 4172 LB 52.3713 5.2580 -3 Leo Barhorst 6 | 4553 CB 53.3210 -2.2330 86 Cees Bassa 7 | 0001 MM 30.3340 -97.7610 160 Mike McCants 8 | 0002 MM 30.3138 -97.8661 280 Mike McCants 9 | 0030 IA 47.4857 18.9727 400 Ivan Artner 10 | 0031 IA 47.5612 18.7366 149 Ivan Artner 11 | 0070 BC 53.2233 -0.6003 30 Bob Christy 12 | 0100 SG 59.4627 17.9138 0 Sven Grahn 13 | 0110 LK 32.5408 -96.8906 200 Lyn Kennedy 14 | 0433 GR -33.9406 18.5129 10 Greg Roberts 15 | 0434 IR -26.1030 27.9288 1646 Ian Roberts 16 | 0435 GR -33.9369 18.5101 25 Greg Roberts 17 | 0710 LS 52.3261 10.6756 85 Lutz Schindler 18 | 0799 PP 36.9608 22.1448 120 Pierros Papadeas 19 | 0899 FM -34.8961 -56.1227 30 Fernando Mederos 20 | 1056 MK 57.0122 23.9833 4 Martins Keruss 21 | 1086 NK 46.4778 30.7556 56 Nikolay Koshkin 22 | 1244 AM 44.3932 33.9701 69 Andriy Makeyev 23 | 1747 DD 45.7275 -72.3526 191 Daniel Deak 24 | 1775 KF 44.6062 -75.6910 200 Kevin Fetter 25 | 2018 PW 51.0945 -1.1188 124 Peter Wakelin 26 | 2115 MW 51.3286 -0.7950 75 Mike Waterman 27 | 2414 DH 50.7468 -1.8800 34 David Hopkins 28 | 2420 RE 55.9486 -3.1386 40 Russell Eberst 29 | 2563 PN 51.0524 2.4043 10 Pierre Nierinck 30 | 2675 DB 52.1358 -2.3264 70 David Brierley 31 | 2701 TM 43.6876 -79.3924 230 Ted Molczan 32 | 2751 BM 51.3440 -1.9849 125 Bruce MacDonald 33 | 2756 AK 56.0907 -3.1623 25 Andy Kirkham 34 | 4353 ML 52.1541 4.4908 0 Marco Langbroek 35 | 4354 ML 52.1168 4.5602 -2 Marco Langbroek 36 | 4355 ML 52.1388 4.4994 -2 Marco Langbroek 37 | 4541 AR 41.9639 12.4531 80 Alberto Rango 38 | 4542 AR 41.9683 12.4545 80 Alberto Rango 39 | 4641 AR 41.1060 16.9010 70 Alberto Rango 40 | 5555 SG 56.1019 94.5533 154 Sergey Guryanov 41 | 5918 BG 59.2985 18.1045 40 Bjorn Gimle 42 | 5919 BG 59.2615 18.6206 30 Bjorn Gimle 43 | 6226 SC 28.4861 -97.8194 110 Scott Campbell 44 | 7777 BY 38.1656 -2.3267 1608 Brad Young remote 45 | 7778 BY -31.2733 149.0644 1122 Brad Young SSO 46 | 7779 BY 32.9204 -105.5283 2225 Brad Young NM 47 | 8048 ST 49.4175 -123.6420 1. Scott Tilley 48 | 8049 ST 49.4348 -123.6685 40. Scott Tilley 49 | 8305 PG 26.2431 -98.2163 30 Paul Gabriel 50 | 8335 BY 36.1397 -95.9838 205 Brad Young 51 | 8336 BY 36.1397 -95.9838 205 Brad Young 52 | 8438 SS 38.8108 -119.7750 1545 Sierra Stars 53 | 8536 TL 36.8479 -76.5010 4 Tim Luton 54 | 8539 SN 39.4707 -79.3388 839 Steve Newcomb 55 | 8597 TB -34.9638 138.6333 100 Tony Beresford 56 | 8600 PC -32.9770 151.6477 18 Paul Camilleri 57 | 0699 PC -14.4733 132.2369 108 Paul Camilleri 58 | 8730 EC 30.3086 -97.7279 150 Ed Cannon 59 | 8739 DB 37.1133 -121.7028 282 Derek Breit 60 | 9461 KO 47.9175 19.89365 938 Konkoly Obs 61 | 9633 JN 33.0206 -96.9982 153 Jim Nix 62 | 9730 MM 30.3150 -97.8660 280 Mike McCants 63 | 6242 JM 42.9453 -2.8284 623 Jon Mikel 64 | 6241 JM 42.9565 -2.8203 619 Jon Mikel 65 | 4160 BD 51.2793 5.4768 35 Bram Dorreman 66 | 9999 GR 47.348 5.5151 100 Graves -------------------------------------------------------------------------------- /orbitdeterminator/test_deploy/README.md: -------------------------------------------------------------------------------- 1 | # Deployment code 2 | This code runs indefinately untill terminated. 3 | ## What it does? 4 | Reads any new file in src folder, and processes it. For testing, it just reads those files. 5 | ## How to test? 6 | There are two parts in this. 7 | * Create new files. `create_files.py` creates a new .txt file every one second and puts it in src folder. 8 | * `deployable.py` Read new files from src and processes it. 9 | 10 | ### Steps: 11 | ** cd to the present directory in your system 12 | 13 | ** run `python3 deployable.py` 14 | 15 | ** run `python3 create_files.py` 16 | 17 | ## Technical details 18 | This can be run continuously for any amount of time with same efficiency provided the load remains the same. 19 | It reads only new files into the buffer so provides better efficiency. 20 | -------------------------------------------------------------------------------- /orbitdeterminator/test_deploy/create_files.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Create a new .txt files every second 3 | ''' 4 | import time 5 | count = 0 6 | while True: 7 | a = open("src/file_%s.txt" % (count), 'w') 8 | a.write("this is file" + str(count)) 9 | count += 1 10 | time.sleep(1) 11 | -------------------------------------------------------------------------------- /orbitdeterminator/test_deploy/deployable.py: -------------------------------------------------------------------------------- 1 | """ 2 | Method: 1. Initialize the source repository with git. 3 | 2. Parse output of git-status to know new files added to this folder. 4 | 3. Append the filenames of these files in a list and process the files. 5 | 4. Write the result in destination folder. 6 | 5. Stage all the files in the list using "git add file1 file2 .." 7 | 6. Repeat steps 2-5 in a loop for near-real time processing. 8 | """ 9 | 10 | import os 11 | import time 12 | import sys 13 | from subprocess import PIPE, run 14 | 15 | 16 | SOURCE_ABSOLUTE = os.getcwd() + "/src" # Absolute path of source directory 17 | os.system("cd %s; git init" % (SOURCE_ABSOLUTE)) 18 | 19 | 20 | def untracked_files(): 21 | """Parses output of `git-status` and returns untracked files. 22 | 23 | Returns: 24 | (string): list of untracked files. 25 | """ 26 | res = run( 27 | "cd %s ; git status" % (SOURCE_ABSOLUTE), 28 | stdout=PIPE, stderr=PIPE, 29 | universal_newlines=True, 30 | shell=True 31 | ) 32 | result = [line.strip() for line in res.stdout.split("\n")] 33 | 34 | files = [file 35 | for file in result if (file.endswith(".txt") 36 | and not (file.startswith("new file") or 37 | file.startswith("deleted") or file.startswith("modified")))] 38 | 39 | return files 40 | 41 | 42 | def stage(processed): 43 | '''Stage the processed files into git file system 44 | 45 | Agrs: 46 | processed (list): List of processed files. 47 | ''' 48 | for file in processed: 49 | print("staging") 50 | run( 51 | "cd %s;git add '%s'" % (SOURCE_ABSOLUTE, file), 52 | stdout=PIPE, stderr=PIPE, 53 | universal_newlines=True, 54 | shell=True 55 | ) 56 | print("File %s has been staged." % (file)) 57 | 58 | def process(file): 59 | for line in file: 60 | print(line) 61 | 62 | def main(): 63 | 64 | number_untracked = 0 65 | while True: 66 | raw_files = untracked_files() 67 | if not raw_files: 68 | if (number_untracked == 0): 69 | print("\nNo unprocessed file found in ./src folder") 70 | else: 71 | print("\nAll untracked files have been processed") 72 | print("Add new files in ./src folder to process them") 73 | time_elapsed = 0 74 | timeout = 30 75 | while (time_elapsed <= timeout and not raw_files): 76 | sys.stdout.write("\r") 77 | sys.stdout.write("-> Timeout in - {:2d} s".format(timeout - time_elapsed)) 78 | sys.stdout.flush() 79 | time.sleep(1) 80 | time_elapsed += 1 81 | raw_files = untracked_files() 82 | sys.stdout.write("\r \n") 83 | pass 84 | if raw_files: 85 | number_untracked += len(raw_files) 86 | for file in raw_files: 87 | print("processing") 88 | with open(SOURCE_ABSOLUTE + "/" + file, "r") as a: 89 | process(a) # Here is where you call the main function. 90 | print("File : %s has been processed \n \n" % file) 91 | stage(raw_files) 92 | continue 93 | print("No new unprocessed file was added, program is now exiting due to timeout!") 94 | print("Total {} untracked files were processed".format(number_untracked)) 95 | break 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /orbitdeterminator/test_deploy/src/test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/test_deploy/src/test.txt -------------------------------------------------------------------------------- /orbitdeterminator/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/tests/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/tests/check_sgp4.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | 5 | from sgp4.earth_gravity import wgs72 6 | from sgp4.io import twoline2rv 7 | from kep_determination.sgp4 import * 8 | 9 | def update_epoch(yr, mth, day, hr, mts, sec): 10 | ''' 11 | Adds one second to the given time. 12 | 13 | Args: 14 | self: class variables 15 | yr: year 16 | mth: month 17 | day: date 18 | hr: hour 19 | mts: minutes 20 | sec: seconds 21 | 22 | Returns: 23 | updated timestamp epoch in a tuple with value as (year, month, day, 24 | hour, minute, second) 25 | ''' 26 | sec += 1 27 | 28 | if(sec >= 60): 29 | sec = 0 30 | mts += 1 31 | 32 | if(mts >= 60): 33 | mts = 0 34 | hr += 1 35 | 36 | if(hr >= 24): 37 | hr = 0 38 | day += 1 39 | 40 | daysInMonth = np.array([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]) 41 | if(yr % 4 == 0): 42 | daysInMonth[1] = 29 43 | 44 | if(day > daysInMonth[mth-1]): 45 | day = 1 46 | mth += 1 47 | 48 | if(mth > 12): 49 | mth = 1 50 | yr += 1 51 | 52 | return yr, mth, day, hr, mts, sec 53 | 54 | if __name__ == "__main__": 55 | line1 = "1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 8" 56 | line2 = "2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 105" 57 | 58 | s = twoline2rv(line1,line2,wgs72) 59 | i = 0 60 | yr,mth,day,hr,mts,sec = 2018,7,21,19,28,41 61 | while(i < 1): 62 | pos,vel = s.propagate(yr,mth,day,hr,mts,sec) 63 | data = [pos[0], pos[1], pos[2], vel[0], vel[1], vel[2]] 64 | data = [float("{0:.5f}".format(i)) for i in data] 65 | print(str(i) + " - " + str(hr) + ":" + str(mts) + ":" + str(sec) + " - " + str(data)) 66 | yr, mth, day, hr, mts, sec = update_epoch(yr, mth, day, hr, mts, sec) 67 | i = i + 1 68 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_check_keplerian.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from kep_determination import lamberts_kalman 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_array_equal 8 | 9 | # First test just checks the function with two normal rows 10 | kep1 = np.array([[15700, 0.3, 90.0, 0.8, 0.0, 28], 11 | [13700, 0.4, 90.0, 0.8, 0.2, 28]]) 12 | 13 | kep2 = np.array([[20000, 0.3, 90.0, 0.8, 0.0, 28], 14 | [13700, 0.4, 90.0, 0.8, 0.2, 28]]) 15 | 16 | 17 | @pytest.mark.parametrize("given, expected", [ 18 | (kep1, kep1), 19 | (kep2, kep2), 20 | ]) 21 | def test_check_keplerian(given, expected): 22 | assert_array_equal(lamberts_kalman.check_keplerian(given), expected) 23 | 24 | 25 | # Second test checks the function with three rows and the first and second row has wrong inputs (a < 0 or e < 0) 26 | kep3 = np.array([[15700, -0.3, 90.0, 0.8, 0.0, 28], 27 | [-13700, 0.4, 90.0, 0.8, 0.2, 28], 28 | [13700, 0.4, 90.0, 0.8, 0.2, 28.]]) 29 | 30 | 31 | def test_check_keplerian_fail(): 32 | with pytest.raises(AssertionError): 33 | assert_array_equal(lamberts_kalman.check_keplerian(kep3), kep3) 34 | 35 | 36 | # Third test just the function with two rows and the second row has wrong inputs (e > 1) 37 | kep4 = np.array([[20000, 0.3, 90.0, 0.8, 0.0, 28.0], 38 | [13741, 1.4, 90.0, 0.8, 0.2, 28.0]]) 39 | 40 | 41 | def test_check_keplerian_fail_2(): 42 | with pytest.raises(AssertionError): 43 | assert_array_equal(lamberts_kalman.check_keplerian(kep4), kep4) 44 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_ellipse_fit.py: -------------------------------------------------------------------------------- 1 | """Tests ellipse_fit with satellites. Compatible with pytest.""" 2 | 3 | import pytest 4 | import numpy as np 5 | import sys 6 | import os.path 7 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 8 | 9 | from util.new_tle_kep_state import tle_to_state 10 | from util.rkf5 import rkf5 11 | from kep_determination.ellipse_fit import determine_kep 12 | 13 | def test_ellipse_fit(): 14 | """Tests ellipse fit with 8 satellites: 15 | * NOAA-1 16 | * GPS-23 17 | * Cryosat-2 18 | * NOAA-15 19 | * NOAA-18 20 | * NOAA-19 21 | * MOLNIYA 2-10 22 | * ISS 23 | 24 | To add your own test copy the template, put the 2nd row of the TLE of the satellite 25 | in place of kep. In the rkf5 line put the final time and time step such that 700±200 26 | points are generated. Now, put the actual orbital parameters in the assert statements. 27 | 28 | Args: 29 | NIL 30 | 31 | Returns: 32 | NIL 33 | """ 34 | 35 | #noaa-1 36 | tle = np.array([101.7540, 195.7370, 0.0031531, 352.8640, 117.2610, 12.53984625169364]) 37 | r = tle_to_state(tle) 38 | _,vecs = rkf5(0,7200,10,r) 39 | r = np.reshape(r,(1,6)) 40 | vecs = np.insert(vecs,0,r,axis=0) 41 | vecs = vecs[:,0:3] 42 | 43 | kep,_ = determine_kep(vecs) 44 | assert kep[0] == pytest.approx(7826.006538, 0.1) # sma 45 | assert kep[1] == pytest.approx(0.0031531, 0.01) # ecc 46 | assert kep[2] == pytest.approx(101.7540, 0.1) # inc 47 | assert kep[3] == pytest.approx(352.8640, 1.0) # argp 48 | assert kep[4] == pytest.approx(195.7370, 0.1) # raan 49 | assert kep[5] == pytest.approx(117.2610, 0.5) # true_anom 50 | 51 | #gps-23 52 | tle = np.array([54.4058, 84.8417, 0.0142955, 74.4543, 193.5934, 2.00565117179872]) 53 | r = tle_to_state(tle) 54 | _,vecs = rkf5(0,43080,50,r) 55 | r = np.reshape(r,(1,6)) 56 | vecs = np.insert(vecs,0,r,axis=0) 57 | vecs = vecs[:,0:3] 58 | 59 | kep,_ = determine_kep(vecs) 60 | assert kep[0] == pytest.approx(26560.21419, 0.1) # sma 61 | assert kep[1] == pytest.approx(0.0142955, 0.01) # ecc 62 | assert kep[2] == pytest.approx(54.4058, 0.1) # inc 63 | assert kep[3] == pytest.approx(74.4543, 1.0) # argp 64 | assert kep[4] == pytest.approx(84.8417, 0.1) # raan 65 | assert kep[5] == pytest.approx(193.5934, 0.5) # true_anom 66 | 67 | #cryosat-2 68 | tle = np.array([92.0287, 282.8216, 0.0005088, 298.0188, 62.0505, 14.52172969429489]) 69 | r = tle_to_state(tle) 70 | _,vecs = rkf5(0,5950,10,r) 71 | r = np.reshape(r,(1,6)) 72 | vecs = np.insert(vecs,0,r,axis=0) 73 | vecs = vecs[:,0:3] 74 | 75 | kep,_ = determine_kep(vecs) 76 | assert kep[0] == pytest.approx(7096.69719, 0.1) # sma 77 | assert kep[1] == pytest.approx(0.0005088, 0.01) # ecc 78 | assert kep[2] == pytest.approx(92.0287, 0.1) # inc 79 | assert kep[3] == pytest.approx(298.0188, 1.0) # argp 80 | assert kep[4] == pytest.approx(282.8216, 0.1) # raan 81 | assert kep[5] == pytest.approx(62.0505, 0.5) # true_anom 82 | 83 | #noaa-15 84 | tle = np.array([98.7705, 158.2195, 0.0009478, 307.8085, 52.2235, 14.25852803]) 85 | r = tle_to_state(tle) 86 | _,vecs = rkf5(0,6120,10,r) 87 | r = np.reshape(r,(1,6)) 88 | vecs = np.insert(vecs,0,r,axis=0) 89 | vecs = vecs[:,0:3] 90 | 91 | kep,_ = determine_kep(vecs) 92 | assert kep[0] == pytest.approx(7183.76381, 0.1) # sma 93 | assert kep[1] == pytest.approx(0.0009478, 0.01) # ecc 94 | assert kep[2] == pytest.approx(98.7705, 0.1) # inc 95 | assert kep[3] == pytest.approx(307.8085, 1.0) # argp 96 | assert kep[4] == pytest.approx(158.2195, 0.1) # raan 97 | assert kep[5] == pytest.approx(52.2235, 0.5) # true_anom 98 | 99 | #noaa-18 100 | tle = np.array([99.1472, 176.6654, 0.0014092, 197.4778, 162.5909, 14.12376102669957]) 101 | r = tle_to_state(tle) 102 | _,vecs = rkf5(0,6120,10,r) 103 | r = np.reshape(r,(1,6)) 104 | vecs = np.insert(vecs,0,r,axis=0) 105 | vecs = vecs[:,0:3] 106 | 107 | kep,_ = determine_kep(vecs) 108 | assert kep[0] == pytest.approx(7229.38911, 0.1) # sma 109 | assert kep[1] == pytest.approx(0.0014092, 0.01) # ecc 110 | assert kep[2] == pytest.approx(99.1472, 0.1) # inc 111 | assert kep[3] == pytest.approx(197.4778, 1.0) # argp 112 | assert kep[4] == pytest.approx(176.6654, 0.1) # raan 113 | assert kep[5] == pytest.approx(162.5909, 0.5) # true_anom 114 | 115 | #noaa-19 116 | tle = np.array([99.1401, 119.3629, 0.0014753, 44.0001, 316.2341, 14.12279464478196]) 117 | r = tle_to_state(tle) 118 | _,vecs = rkf5(0,6120,10,r) 119 | r = np.reshape(r,(1,6)) 120 | vecs = np.insert(vecs,0,r,axis=0) 121 | vecs = vecs[:,0:3] 122 | 123 | kep,_ = determine_kep(vecs) 124 | assert kep[0] == pytest.approx(7229.71889, 0.1) # sma 125 | assert kep[1] == pytest.approx(0.0014753, 0.01) # ecc 126 | assert kep[2] == pytest.approx(99.1401, 0.1) # inc 127 | assert kep[3] == pytest.approx(44.0001, 1.0) # argp 128 | assert kep[4] == pytest.approx(119.3629, 0.1) # raan 129 | assert kep[5] == pytest.approx(316.2341, 0.5) # true_anom 130 | 131 | #molniya 2-10 132 | tle = np.array([63.2749, 254.2968, 0.7151443, 294.4926, 9.2905, 2.01190064320534]) 133 | r = tle_to_state(tle) 134 | _,vecs = rkf5(0,43000,50,r) 135 | r = np.reshape(r,(1,6)) 136 | vecs = np.insert(vecs,0,r,axis=0) 137 | vecs = vecs[:,0:3] 138 | 139 | kep,_ = determine_kep(vecs) 140 | assert kep[0] == pytest.approx(26505.1836, 0.1) # sma 141 | assert kep[1] == pytest.approx(0.7151443, 0.01) # ecc 142 | assert kep[2] == pytest.approx(63.2749, 0.1) # inc 143 | assert kep[3] == pytest.approx(294.4926, 1.0) # argp 144 | assert kep[4] == pytest.approx(254.2968, 0.1) # raan 145 | assert kep[5] == pytest.approx(65.56742, 0.5) # true_anom 146 | 147 | #ISS 148 | tle = np.array([51.6402, 150.4026, 0.0004084, 108.2140, 238.0528, 15.54082454114406]) 149 | r = tle_to_state(tle) 150 | _,vecs = rkf5(0,5560,10,r) 151 | r = np.reshape(r,(1,6)) 152 | vecs = np.insert(vecs,0,r,axis=0) 153 | vecs = vecs[:,0:3] 154 | 155 | kep,_ = determine_kep(vecs) 156 | assert kep[0] == pytest.approx(6782.95812, 0.1) # sma 157 | assert kep[1] == pytest.approx(0.0004084, 0.01) # ecc 158 | assert kep[2] == pytest.approx(51.6402, 0.1) # inc 159 | assert kep[3] == pytest.approx(108.2140, 1.0) # argp 160 | assert kep[4] == pytest.approx(150.4026, 0.1) # raan 161 | assert kep[5] == pytest.approx(238.0528, 0.5) # true_anom 162 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) 4 | 5 | from orbitdeterminator.filters import triple_moving_average as tma 6 | import pytest 7 | 8 | def test_weighted_average(): 9 | assert tma.weighted_average([1, 1, 1]) == 1 10 | assert tma.weighted_average([0, 0, 0]) == 0 11 | assert tma.weighted_average([10]) == 10 12 | with pytest.raises(ZeroDivisionError): 13 | tma.weighted_average([]) -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_gibbsMethod.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | 5 | import numpy as np 6 | from numpy.testing import assert_array_equal 7 | from numpy.testing import assert_almost_equal 8 | from kep_determination.gibbs_method import * 9 | 10 | def test_convert_list(): 11 | obj = Gibbs() 12 | 13 | vec1 = ['12346.23434', '123.456', '456.789', '789.123'] 14 | vec = obj.convert_list(vec1) 15 | assert(isinstance(vec[0], float) == True) 16 | assert(isinstance(vec[1], float) == True) 17 | assert(isinstance(vec[2], float) == True) 18 | 19 | vec1 = ['5644.23456', '2634.1234', '74653.34958', '685476.2345'] 20 | vec = obj.convert_list(vec1) 21 | assert(isinstance(vec[0], float) == True) 22 | assert(isinstance(vec[1], float) == True) 23 | assert(isinstance(vec[2], float) == True) 24 | 25 | del(obj) 26 | 27 | def test_magnitude(): 28 | obj = Gibbs() 29 | 30 | vec = [-294.32, 4265.1, 5986.7] 31 | mag = np.linalg.norm(vec) 32 | mag = float("{0:.1f}".format(mag)) 33 | ans = 7356.5 34 | assert_almost_equal(mag, ans) 35 | 36 | vec = [-1365.5, 3637.6, 6346.8] 37 | mag = np.linalg.norm(vec) 38 | mag = float("{0:.1f}".format(mag)) 39 | ans = 7441.7 40 | assert_almost_equal(mag, ans) 41 | 42 | vec = [-2940.3, 2473.7, 6555.8] 43 | mag = np.linalg.norm(vec) 44 | mag = float("{0:.1f}".format(mag)) 45 | ans = 7598.9 46 | assert_almost_equal(mag, ans) 47 | 48 | del(obj) 49 | 50 | def test_dot_product(): 51 | obj = Gibbs() 52 | 53 | vec1 = [123.76, 233.98, 675.21] 54 | vec2 = [13.234, 231.235, 8776.65] 55 | dot = np.dot(vec1, vec2) 56 | dot = float("{0:.2f}".format(dot)) 57 | ans = 5981824.05 58 | assert_almost_equal(dot, ans) 59 | 60 | vec1 = [435.3452, -655.621, 956.075] 61 | vec2 = [537.956, 392.374, -755.343] 62 | dot = np.dot(vec1, vec2) 63 | dot = float("{0:.2f}".format(dot)) 64 | ans = -745216.63 65 | assert_almost_equal(dot, ans) 66 | 67 | del(obj) 68 | 69 | def test_cross_product(): 70 | obj = Gibbs() 71 | 72 | vec1 = [123.76, 233.98, 675.21] 73 | vec2 = [13.234, 231.235, 8776.65] 74 | cross = np.cross(vec1, vec2) 75 | cross = [float("{0:.2f}".format(i)) for i in cross] 76 | ans = [1897428.38, -1077262.47, 25521.15] 77 | assert_array_equal(cross, ans) 78 | 79 | vec1 = [435.3452, -655.621, 956.075] 80 | vec2 = [537.956, 392.374, -755.343] 81 | cross = np.cross(vec1, vec2) 82 | cross = [float("{0:.2f}".format(i)) for i in cross] 83 | ans = [120079.76, 843161.23, 523513.39] 84 | assert_array_equal(cross, ans) 85 | 86 | del(obj) 87 | 88 | def test_operate_vector(): 89 | obj = Gibbs() 90 | 91 | vec1 = [123.76, 233.98, 675.21] 92 | vec2 = [13.234, 231.235, 8776.65] 93 | 94 | vec = obj.operate_vector(vec1, vec2, 1) 95 | vec = [float("{0:.2f}".format(i)) for i in vec] 96 | ans = [136.99, 465.22, 9451.86] 97 | assert_array_equal(vec, ans) 98 | 99 | vec = obj.operate_vector(vec1, vec2, 0) 100 | vec = [float("{0:.2f}".format(i)) for i in vec] 101 | ans = [110.53, 2.74, -8101.44] 102 | assert_array_equal(vec, ans) 103 | 104 | vec1 = [435.3452, -655.621, 956.075] 105 | vec2 = [537.956, 392.374, -755.343] 106 | 107 | vec = obj.operate_vector(vec1, vec2, 1) 108 | vec = [float("{0:.2f}".format(i)) for i in vec] 109 | ans = [973.3, -263.25, 200.73] 110 | assert_array_equal(vec, ans) 111 | 112 | vec = obj.operate_vector(vec1, vec2, 0) 113 | vec = [float("{0:.2f}".format(i)) for i in vec] 114 | ans = [-102.61, -1047.99, 1711.42] 115 | assert_array_equal(vec, ans) 116 | 117 | del(obj) 118 | 119 | 120 | def test_unit(): 121 | obj = Gibbs() 122 | 123 | vec = [8.1473, -9.7095, 7.3179] 124 | unit = obj.unit(vec) 125 | unit = [float("{0:.5f}".format(i)) for i in unit] 126 | ans = [0.55667, -0.66341, 0.5] 127 | assert_array_equal(unit, ans) 128 | 129 | vec = [-294.32, 4265.1, 5986.7] 130 | unit = obj.unit(vec) 131 | unit = [float("{0:.5f}".format(i)) for i in unit] 132 | ans = [-0.04001, 0.57977, 0.8138] 133 | assert_array_equal(unit, ans) 134 | 135 | del(obj) 136 | 137 | def test_gibbs(): 138 | obj = Gibbs() 139 | 140 | r1 = [-294.32, 4265.1, 5986.7] 141 | r2 = [-1365.5, 3637.6, 6346.8] 142 | r3 = [-2940.3, 2473.7, 6555.8] 143 | v = obj.gibbs(r1, r2, r3) 144 | v = [float("{0:.4f}".format(i)) for i in v] 145 | ans = [-6.2174, -4.0122, 1.599] 146 | assert_array_equal(v, ans) 147 | 148 | del(obj) 149 | 150 | def test_orbital_elements(): 151 | obj = Gibbs() 152 | 153 | r = [-1365.5, 3637.6, 6346.8] 154 | v = [-6.2174, -4.0122, 1.599] 155 | ele = obj.orbital_elements(r, v) 156 | ele = [float("{0:.2f}".format(i)) for i in ele] 157 | # Old format not in consistence with main.py 158 | # ans = [8001.48, 60.0, 40.0, 0.1, 30.08, 49.92] 159 | # New format in consistence with main.py 160 | ans = [8001.48, 0.1, 60.0, 30.08, 40.0, 49.92] 161 | assert_array_equal(ele, ans) 162 | 163 | r = [-6045, -3490, 2500] 164 | v = [-3.457, 6.618, 2.533] 165 | ele = obj.orbital_elements(r, v) 166 | ele = [float("{0:.2f}".format(i)) for i in ele] 167 | # Old format not in consistence with main.py 168 | # ans = [8788.08, 153.25, 255.28, 0.17, 20.07, 28.45] 169 | # New format in consistence with main.py 170 | ans = [8788.08, 0.17, 153.25, 20.07, 255.28, 28.45] 171 | assert_array_equal(ele, ans) 172 | 173 | r = [5.0756899358316559e+03, -4.5590381308371752e+03, 1.9322228177731663e+03] 174 | v = [1.3360847905126974e+00, -1.5698574946888049e+00, -7.2117328822023676e+00] 175 | ele = obj.orbital_elements(r, v) 176 | ele = [float("{0:.2f}".format(i)) for i in ele] 177 | # Old format not in consistence with main.py 178 | # ans = [7096.68, 92.02, 137.5, 0.0, 159.0, 5.17] 179 | # New format in consistence with main.py 180 | ans = [7096.68, 0.0, 92.02, 159.0, 137.5, 5.17] 181 | assert_array_equal(ele, ans) 182 | 183 | del(obj) 184 | 185 | if __name__ == "__main__": 186 | test_convert_list() 187 | test_magnitude() 188 | test_dot_product() 189 | test_cross_product() 190 | test_operate_vector() 191 | test_unit() 192 | test_gibbs() 193 | test_orbital_elements() 194 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_input_transf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from util import input_transf 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_array_equal 8 | 9 | 10 | # This test takes one array with cartesian coordinates transform it into spherical coordinates 11 | # with cart_to_spher function and then apply to that computed array the vice versa process to 12 | # see if the final results if the initial array 13 | 14 | def test_imput_transf(): 15 | expected = np.array([[0, 1000, 1000, 2000]]) 16 | given = input_transf.cart_to_spher(expected) 17 | 18 | assert_array_equal(input_transf.spher_to_cart(given), expected) 19 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_kalman.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from kep_determination import lamberts_kalman 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_array_equal 8 | 9 | # The first test checks the fact that if we give to the kalman filter three identical sets of keplerian elements 10 | # the final approximation will be equal to this set 11 | 12 | 13 | def test_kalman(): 14 | kep1 = np.array([[10000, 0.10, 90.0, 0.80, 0.0, 28.00], 15 | [10000, 0.10, 90.0, 0.80, 0.0, 28.00], 16 | [10000, 0.10, 90.0, 0.80, 0.0, 28.00]]) 17 | 18 | assert_array_equal(np.ravel(lamberts_kalman.kalman(kep1, 0.01**2)), kep1[0, :]) 19 | 20 | 21 | # The second test checks the fact that if we change the R parameter for the same given set it produces a different 22 | # result 23 | 24 | 25 | def test_kalman_fail(): 26 | kep2 = np.array([[10000, 0.10, 90.0, 0.80, 0.0, 28.00], 27 | [12000, 0.20, 92.0, 0.82, 0.1, 28.20], 28 | [13500, 0.15, 95.0, 0.85, 0.3, 28.50]]) 29 | 30 | given = lamberts_kalman.kalman(kep2, 0.01 ** 2) 31 | expected = lamberts_kalman.kalman(kep2, 0.001 ** 2) 32 | 33 | with pytest.raises(AssertionError): 34 | assert_array_equal(given, expected) 35 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_lamberts_kalman.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from kep_determination import (lamberts_kalman, interpolation) 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_array_equal 8 | 9 | 10 | @pytest.fixture() 11 | def my_kep(): 12 | kep = np.array([[100, 10100, 9500, 20500], 13 | [200, 10500, 9200, 20700], 14 | ], dtype=float) 15 | return kep 16 | 17 | 18 | # Checks if the output lenght is 6 == keplerian elements number 19 | def test_output_len(my_kep): 20 | assert len(np.ravel(lamberts_kalman.create_kep(my_kep))) == 6 21 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_orbit_trajectory.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from kep_determination import lamberts_kalman 5 | import numpy as np 6 | import pytest 7 | from numpy.testing import assert_array_equal 8 | 9 | 10 | # This test checks the function by giving two points to it. In the first case we give the points as point1 and point2 11 | # and it returns True because the motion is retrogade. Now if we put the points in reverse order we expect to get 12 | # a false value meaning that the motion to be counter-clock wise. 13 | x1 = np.array([0.0, 9590.0, -16.0, -80.0]) 14 | x2 = np.array([100.0, 9593.0, -35.0, 740.0]) 15 | x1_new = [1, 1, 1] 16 | x1_new[:] = x1[1:4] 17 | x2_new = [1, 1, 1] 18 | x2_new[:] = x2[1:4] 19 | time = x2[0] - x1[0] 20 | 21 | 22 | @pytest.mark.parametrize("givenx1,givenx2,giventime, expected", [ 23 | (x1_new, x2_new, time, True), 24 | (x2_new, x1_new, time, False) 25 | ]) 26 | def test_orbit_trajectory(givenx1, givenx2, giventime, expected): 27 | assert_array_equal(lamberts_kalman.orbit_trajectory(givenx1, givenx2, giventime), expected) 28 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_sav_golay.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from filters import sav_golay 5 | import numpy as np 6 | from numpy.testing import assert_array_equal 7 | import pytest 8 | 9 | 10 | @pytest.fixture 11 | def my_data(): 12 | data = np.array([[100, 1000, 2000, 3000], 13 | [200, 1500, 500, 2500], 14 | [300, 1700, 200, 2200], 15 | [400, 1800, 100, 2000], 16 | [500, 1900, 20, 1700] 17 | ]) 18 | return data 19 | 20 | 21 | # Checks that a change in the window parameter of the golay filter changes the result 22 | def test_golay_params(my_data): 23 | given = sav_golay.golay(my_data, 3, 1) 24 | expected = sav_golay.golay(my_data, 5, 1) 25 | with pytest.raises(AssertionError): 26 | assert_array_equal(given, expected) 27 | 28 | 29 | # Checks that the input and output data are of the same length 30 | def test_golay_length(my_data): 31 | given = len(my_data) 32 | expected = len(sav_golay.golay(my_data, 3, 1)) 33 | assert given == expected 34 | 35 | 36 | # Checks that if you input an odd integer for window lenght it raises an error 37 | def test_golay_parmerrors(my_data): 38 | with pytest.raises(Exception): 39 | result = sav_golay.golay(my_data, 2, 1) 40 | 41 | 42 | # Checks that if you input an integer for the polynomial parameter equal or larger than the window lenght ir raises 43 | # an error 44 | def test_golay_parmerrors(my_data): 45 | with pytest.raises(Exception): 46 | result = sav_golay.golay(my_data, 3, 3) 47 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_sgp4.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | 5 | from numpy.testing import assert_array_equal 6 | from propagation.sgp4 import * 7 | 8 | def test_proapagation_model(): 9 | obj = SGP4() 10 | 11 | line1 = "1 88888U 80275.98708465 .00073094 13844-3 66816-4 0 8" 12 | line2 = "2 88888 72.8435 115.9689 0086731 52.6988 110.5714 16.05824518 105" 13 | obj.compute_necessary_tle(line1, line2) 14 | tsince = 0 15 | r,v = obj.propagation_model(tsince) 16 | r = [float("{0:.5f}".format(i)) for i in r] 17 | v = [float("{0:.5f}".format(i)) for i in v] 18 | pos = [2328.97070, -5995.22083, 1719.97066] 19 | vel = [2.91207, -0.98342, -7.09082] 20 | assert_array_equal(r, pos) 21 | assert_array_equal(v, vel) 22 | # print(r,v) 23 | 24 | line1 = "1 32785U 08021C 18201.86927515 .00000199 00000-0 27157-4 0 9996" 25 | line2 = "2 32785 97.5464 212.4389 0011563 289.3405 70.6562 14.88147354554182" 26 | obj.compute_necessary_tle(line1, line2) 27 | tsince = 0 28 | r,v = obj.propagation_model(tsince) 29 | r = [float("{0:.5f}".format(i)) for i in r] 30 | v = [float("{0:.5f}".format(i)) for i in v] 31 | pos = [-5892.75718, -3745.27150, 0.00265] 32 | vel = [-0.53231, 0.83713, 7.49348] 33 | assert_array_equal(r, pos) 34 | assert_array_equal(v, vel) 35 | # print(r,v) 36 | 37 | line1 = "1 27844U 03031E 18209.96155204 .00000018 00000-0 28062-4 0 9995" 38 | line2 = "2 27844 98.6862 218.0011 0008601 248.4534 111.5728 14.22124843782096" 39 | obj.compute_necessary_tle(line1, line2) 40 | tsince = 0 41 | r,v = obj.propagation_model(tsince) 42 | r = [float("{0:.5f}".format(i)) for i in r] 43 | v = [float("{0:.5f}".format(i)) for i in v] 44 | pos = [-5674.78436, -4433.80110, 0.00928] 45 | vel = [-0.69002, 0.88593, 7.35498] 46 | assert_array_equal(r, pos) 47 | assert_array_equal(v, vel) 48 | # print(r,v) 49 | 50 | del(obj) 51 | 52 | def test_recover_tle(): 53 | obj = SGP4() 54 | 55 | pos = [2.32897070e+03, -5.99522083e+03, 1.71997066e+03] 56 | vel = [2.91207000e+00, -9.83420000e-01, -7.09082000e+00] 57 | tle = obj.recover_tle(pos, vel) 58 | line1 = '1 xxxxxc xxxxxccc xxxxx.xxxxxxxx _.xxxxxxxx _xxxxx_x _xxxxx_x x xxxxx' 59 | line2 = '2 xxxxx 72.8539 115.9623 0096686 59.4225 104.8919 16.03893203xxxxxx' 60 | # print(tle[0]) 61 | # print(tle[1]) 62 | assert(tle[0] == line1) 63 | assert(tle[1] == line2) 64 | 65 | del(obj) 66 | 67 | if __name__ == "__main__": 68 | test_proapagation_model() 69 | test_recover_tle() 70 | -------------------------------------------------------------------------------- /orbitdeterminator/tests/test_statekep_kepstate.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 4 | from util import (state_kep, kep_state) 5 | import numpy as np 6 | import numpy.testing as npt 7 | 8 | 9 | # Test the vice versa procedure with an uncertainty of 10 decimal points 10 | def test_statekep_kepstate(): 11 | # Initial state vector which is going to be transformed into keplerian elements and then back to the initial state 12 | kep = np.array([[15711.578566], [0.377617], [90.0], [0.887383], [0.0], [28.357744]]) 13 | y = kep_state.kep_state(kep) 14 | 15 | expected = np.ravel(kep) 16 | r_given = np.array([y[0, 0], y[1, 0], y[2, 0]]) 17 | v_given = np.array([y[3, 0], y[4, 0], y[5, 0]]) 18 | 19 | npt.assert_almost_equal(state_kep.state_kep(r_given, v_given), expected, decimal=10) 20 | 21 | -------------------------------------------------------------------------------- /orbitdeterminator/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aerospaceresearch/orbitdeterminator/1aec8919ba42978e73aab4eaefe407adeb6287e9/orbitdeterminator/util/__init__.py -------------------------------------------------------------------------------- /orbitdeterminator/util/anom_conv.py: -------------------------------------------------------------------------------- 1 | """Vectorized anomaly conversion scripts""" 2 | 3 | import numpy as np 4 | 5 | def true_to_ecc(theta,e): 6 | """Converts true anomaly to eccentric anomaly. 7 | 8 | Args: 9 | theta(numpy array): array of true anomalies (in radians) 10 | e(float): eccentricity 11 | 12 | Returns: 13 | numpy array: array of eccentric anomalies (in radians) 14 | """ 15 | 16 | pi2=2*np.pi 17 | raw = np.arctan2((1-e**2)*np.sin(theta), e+np.cos(theta)) 18 | raw = raw%pi2 19 | 20 | # revolution correction code 21 | correct = (theta//pi2)*pi2 22 | raw = raw+correct 23 | correct = np.clip((raw-theta)//pi2,0,None)*pi2 24 | 25 | return raw-correct 26 | 27 | def ecc_to_mean(E,e): 28 | """Converts eccentric anomaly to mean anomaly. 29 | 30 | Args: 31 | E(numpy array): array of eccentric anomalies (in radians) 32 | e(float): eccentricity 33 | 34 | Returns: 35 | numpy array: array of mean anomalies (in radians) 36 | """ 37 | 38 | return E - e*np.sin(E) 39 | 40 | def mean_to_t(M,a): 41 | """Converts mean anomaly to time elapsed. 42 | 43 | Args: 44 | M(numpy array): array of mean anomalies (in radians) 45 | a(float): semi-major axis 46 | 47 | Returns: 48 | numpy array: numpy array of time elapsed 49 | """ 50 | 51 | n = (a**3/398600.4405)**0.5 52 | return n*(M-M[0]) 53 | 54 | if __name__ == "__main__": 55 | thetas = np.linspace(0,4*np.pi,21) 56 | e = 0.7151443 57 | print(thetas) 58 | ecc = true_to_ecc(thetas,e) 59 | print(ecc) 60 | mean = ecc_to_mean(ecc,e) 61 | print(mean) 62 | t = mean_to_t(mean,26505.1836) 63 | print(t) 64 | -------------------------------------------------------------------------------- /orbitdeterminator/util/golay_window.py: -------------------------------------------------------------------------------- 1 | def window(error, data): 2 | ''' 3 | Calculates the constant c which is needed to determine the savintzky - golay filter window 4 | window = len(data) / c ,where c is a constant strongly related to the error contained in the data set 5 | 6 | Args: 7 | error(float): the a-priori error estimation for each measurment 8 | data(numpy array): the positional data set 9 | 10 | Returns: 11 | float: constant which describes the window that needs to be inputed to the savintzky - golay filter 12 | 13 | ''' 14 | if error <= 40.0: 15 | c = 10.26 + (10676069.73 / (1 + ((error/1.242)**5.367))) 16 | else: 17 | c = (- 0.046725 * error) + 13.102 18 | 19 | c = int(c) 20 | 21 | window = len(data) / c 22 | window = int(window) 23 | if (window % 2) == 0: 24 | window = window + 1 25 | 26 | return window 27 | 28 | 29 | if __name__ == "__main__": 30 | x_error = 10 # x_error = 10 means, 10km a-priori error estimation, for points with time difference of 1 second 31 | print(window(x_error)) 32 | -------------------------------------------------------------------------------- /orbitdeterminator/util/input_transf.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Converts cartesian co-ordinates to spherical co-ordinates and vice versa 3 | ''' 4 | 5 | import sys 6 | import os 7 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 8 | from util import read_data 9 | from math import * 10 | 11 | def cart_to_spher(data): 12 | ''' 13 | Takes as an input a data set containing points in cartesian format (time, x, y, z) and returns the computed 14 | spherical coordinates (time, azimuth, elevation, r) 15 | 16 | Args: 17 | data (numpy array): containing the cartesian coordinates in format of (time, x, y, z) 18 | 19 | Returns: 20 | numpy array: array of spherical coordinates in format of (time, azimuth, elevation, r) 21 | ''' 22 | 23 | for i in range(0, len(data)): 24 | x = data[i, 1] 25 | y = data[i, 2] 26 | z = data[i, 3] 27 | 28 | r = sqrt(x**2 + y**2 + z**2) 29 | elevation = atan2(z, sqrt(x**2 + y**2)) 30 | azimuth = atan2(y, x) 31 | result = data 32 | result[i, 1] = azimuth 33 | result[i, 2] = elevation 34 | result[i, 3] = r 35 | 36 | return result 37 | 38 | 39 | def spher_to_cart(data): 40 | ''' 41 | Takes as an input a data set containing points in spherical format (time, azimuth, elevation, r) and 42 | returns the computed cartesian coordinates (time, x, y, z). 43 | 44 | Args: 45 | data (numpy array): containing the spherical coordinates in format of (time, azimuth, elevation, r) 46 | 47 | Returns: 48 | numpy array: array of cartesian coordinates in format of (time, x, y, z) 49 | ''' 50 | 51 | for i in range(0, len(data)): 52 | elevation = data[i, 1] 53 | azimuth = data[i, 2] 54 | r = data[i, 3] 55 | 56 | x = r * cos(elevation) * cos(azimuth) 57 | y = r * cos(elevation) * sin(azimuth) 58 | z = r * sin(elevation) 59 | 60 | result = data 61 | result[i, 1] = x 62 | result[i, 2] = y 63 | result[i, 3] = z 64 | 65 | return result 66 | 67 | if __name__ == "__main__": 68 | 69 | data = read_data.load_data("../example_data/orbit.csv") 70 | new_data = cart_to_spher(data) 71 | same_data = spher_to_cart(new_data) 72 | print(same_data == data) 73 | 74 | -------------------------------------------------------------------------------- /orbitdeterminator/util/kep_state.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Takes a set of keplerian elements (a, e, i, ω, Ω, v) and transforms it into a state vector 3 | (x, y, z, vx, vy, vz) where v is the velocity of the satellite 4 | ''' 5 | 6 | import numpy as np 7 | from math import * 8 | 9 | 10 | def kep_state(kep): 11 | ''' 12 | Converts the keplerian elements to position and velocity vector 13 | 14 | Args: 15 | kep(numpy array): a 1x6 matrix which contains the following variables 16 | kep(0): semi major axis (km) 17 | kep(1): eccentricity (number) 18 | kep(2): inclination (degrees) 19 | kep(3): argument of perigee (degrees) 20 | kep(4): right ascension of the ascending node (degrees) 21 | kep(5): true anomaly (degrees) 22 | 23 | Returns: 24 | numpy array: 1x6 matrix which contains the position and velocity vector 25 | r(0),r(1),r(2): position vector (x,y,z) km 26 | r(3),r(4),r(5): velocity vector (vx,vy,vz) km/s 27 | ''' 28 | 29 | r = np.zeros((6, 1)) 30 | mu = 398600.4405 31 | 32 | # unload orbital elements array 33 | 34 | sma = kep[0, 0] 35 | ecc = kep[1, 0] 36 | inc = kep[2, 0] 37 | inc = radians(inc) 38 | argper = kep[3, 0] 39 | argper = radians(argper) 40 | raan = kep[4, 0] 41 | raan = radians(raan) 42 | tanom = kep[5, 0] 43 | tanom = radians(tanom) 44 | 45 | slr = sma * (1 - ecc * ecc) 46 | rm = slr / (1 + ecc * cos(tanom)) 47 | 48 | arglat = argper + tanom # argument of latitude 49 | 50 | sarglat = sin(arglat) 51 | carglat = cos(arglat) 52 | 53 | c4 = sqrt(mu / slr) 54 | c5 = ecc * cos(argper) + carglat 55 | c6 = ecc * sin(argper) + sarglat 56 | 57 | sinc = sin(inc) 58 | cinc = cos(inc) 59 | 60 | sraan = sin(raan) 61 | craan = cos(raan) 62 | 63 | # position vector 64 | r[0, 0] = rm * (craan * carglat - sraan * cinc * sarglat) 65 | r[1, 0] = rm * (sraan * carglat + cinc * sarglat * craan) 66 | r[2, 0] = rm * sinc * sarglat 67 | 68 | # velocity vector 69 | 70 | r[3, 0] = -c4 * (craan * c6 + sraan * cinc * c5) 71 | r[4, 0] = -c4 * (sraan * c6 - craan * cinc * c5) 72 | r[5, 0] = c4 * c5 * sinc 73 | 74 | # # transform r and v into ECI frame 75 | # 76 | # R1inc = np.array([[1, 0, 0], 77 | # [0, cos(-inc), sin(-inc)], 78 | # [0, -sin(-inc), cos(-inc)] 79 | # ]) 80 | # R3raan = np.array([[cos(-raan), sin(-raan), 0], 81 | # [-sin(-raan), cos(-raan), 0], 82 | # [0, 0, 1] 83 | # ]) 84 | # R3argper = np.array([[cos(-argper), sin(-argper), 0], 85 | # [-sin(-argper), cos(-argper), 0], 86 | # [0, 0, 1] 87 | # ]) 88 | # 89 | # r_final1 = np.dot(R3raan, R1inc) 90 | # r_final2 = np.dot(R3argper, r[0:3]) 91 | # 92 | # r_final = np.dot(r_final1, r_final2) 93 | # print(r_final) 94 | # v_final1 = np.dot(R3raan, R1inc) 95 | # v_final2 = np.dot(R3argper, r[3:6]) 96 | # v_final = np.dot(v_final1, v_final2) 97 | # print(v_final) 98 | # 99 | # r[0:3] = r_final 100 | # r[3:6] = v_final 101 | return r 102 | 103 | if __name__ == "__main__": 104 | kep = np.array([[15711.578566], [0.377617], [90.0], [0.887383], [0.0], [28.357744]]) 105 | r = kep_state(kep) 106 | print(r) 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /orbitdeterminator/util/new_tle_kep_state.py: -------------------------------------------------------------------------------- 1 | """This module computes the state vector from keplerian elements.""" 2 | 3 | import math 4 | import numpy as np 5 | from scipy.optimize import fsolve 6 | 7 | mu = 398600.4418 8 | 9 | def __MtoE(M,e): 10 | """Calculates the eccentric anomaly from the mean anomaly. 11 | 12 | Args: 13 | M(float): the mean anomaly (in radians) 14 | e(float): the eccentricity 15 | 16 | Returns: 17 | float: The eccentric anomaly (in radians) 18 | """ 19 | 20 | E = M 21 | dy = 1 22 | while(abs(dy) > 0.0001): 23 | M2 = E - e*math.sin(E) 24 | dy = M - M2 25 | dx = dy/(1-e*math.cos(E)) 26 | E = E+dx 27 | 28 | return E 29 | 30 | def __TtoE(T,e): 31 | E = math.atan2((1-e**2)**0.5*math.sin(T),e+math.cos(T)) 32 | E = E%(2*math.pi) 33 | return E 34 | 35 | def __EtoT(E,e): 36 | T = math.atan2((1-e**2)**0.5*math.sin(E),math.cos(E)-e) 37 | T = T%(2*math.pi) 38 | return T 39 | 40 | def MtoT(M,e): 41 | return __EtoT(__MtoE(M,e),e) 42 | 43 | def tle_to_state(tle): 44 | """ This function converts from TLE elements to position and velocity vector 45 | 46 | Args: 47 | tle(1x6 numpy array): tle contains the following variables 48 | tle[0] = inclination (degrees) 49 | tle[1] = right ascension of the ascending node (degrees) 50 | tle[2] = eccentricity (number) 51 | tle[3] = argument of perigee (degrees) 52 | tle[4] = mean anomaly (degrees) 53 | tle[5] = mean motion (revs per day) 54 | 55 | Returns: 56 | r: 1x6 numpy array which contains the position and velocity vector 57 | r[0],r[1],r[2] = position vector [rx,ry,rz] km 58 | r[3],r[4],r[5] = velocity vector [vx,vy,vz] km/s 59 | """ 60 | 61 | # unload orbital elements array 62 | T = 86400/tle[5] 63 | sma = (mu*(T/2/math.pi)**2)**(1/3) 64 | ecc = tle[2] # eccentricity 65 | inc = tle[0] # inclination 66 | argp = tle[3] # argument of perigee 67 | raan = tle[1] # right ascension of the ascending node 68 | tanom = MtoT(math.radians(tle[4]), ecc) # we use mean anomaly(kep(5)) and the function MtoT to compute true anomaly (tanom) 69 | tanom = math.degrees(tanom)%360 70 | 71 | kep = np.array([sma,ecc,inc,argp,raan,tanom]) 72 | s = kep_to_state(kep) 73 | 74 | print("Keplerian Elements:") 75 | print(kep) 76 | 77 | return s 78 | 79 | def kep_to_state(kep): 80 | """ This function converts from keplerian elements to the position and velocity vector 81 | 82 | Args: 83 | kep(1x6 numpy array): kep contains the following variables 84 | kep[0] = semi-major axis (kms) 85 | kep[1] = eccentricity (number) 86 | kep[2] = inclination (degrees) 87 | kep[3] = argument of perigee (degrees) 88 | kep[4] = right ascension of ascending node (degrees) 89 | kep[5] = true anomaly (degrees) 90 | 91 | Returns: 92 | r: 1x6 numpy array which contains the position and velocity vector 93 | r[0],r[1],r[2] = position vector [rx,ry,rz] km 94 | r[3],r[4],r[5] = velocity vector [vx,vy,vz] km/s 95 | """ 96 | 97 | r = np.zeros((6,1)) 98 | 99 | # unload orbital elements array 100 | 101 | sma = kep[0] # sma is semi major axis 102 | ecc = kep[1] # eccentricity 103 | inc = math.radians(kep[2]) # inclination 104 | argp = math.radians(kep[3]) # argument of perigee 105 | raan = math.radians(kep[4]) # right ascension of the ascending node 106 | eanom = __TtoE(math.radians(kep[5]), ecc) # we use mean anomaly(kep(5)) and the function MtoE to compute eccentric anomaly (eanom) 107 | 108 | smb = sma * math.sqrt(1-ecc**2) 109 | 110 | x = sma * (math.cos(eanom) - ecc) 111 | y = smb * math.sin(eanom) 112 | 113 | # calculate position and velocity in orbital frame 114 | m_dot = (mu/sma**3)**0.5 115 | e_dot = m_dot/(1 - ecc*math.cos(eanom)) 116 | x_dot = -sma * math.sin(eanom) * e_dot 117 | y_dot = smb * math.cos(eanom) * e_dot 118 | 119 | # rotate them by argp degrees 120 | x_rot = x * math.cos(argp) - y * math.sin(argp) 121 | y_rot = x * math.sin(argp) + y * math.cos(argp) 122 | x_dot_rot = x_dot * math.cos(argp) - y_dot * math.sin(argp) 123 | y_dot_rot = x_dot * math.sin(argp) + y_dot * math.cos(argp) 124 | 125 | # convert them into 3D coordinates 126 | r[0] = x_rot * math.cos(raan) - y_rot * math.sin(raan) * math.cos(inc) 127 | r[1] = x_rot * math.sin(raan) + y_rot * math.cos(raan) * math.cos(inc) 128 | r[2] = y_rot * math.sin(inc) 129 | 130 | r[3] = x_dot_rot * math.cos(raan) - y_dot_rot * math.sin(raan) * math.cos(inc) 131 | r[4] = x_dot_rot * math.sin(raan) + y_dot_rot * math.cos(raan) * math.cos(inc) 132 | r[5] = y_dot_rot * math.sin(inc) 133 | 134 | return r 135 | 136 | if __name__ == "__main__": 137 | 138 | #tle = np.array([51.6382,7.1114,0.0002893,211.1340,148.9642,15.568214688]) 139 | #tle = np.array([51.6428, 291.0075, 0.0003411, 263.9950, 245.8448, 15.54009155]) 140 | tle = np.array([51.6418, 266.6543, 0.0003456, 290.0933, 212.4518, 15.54021918]) 141 | r = tle_to_state(tle) 142 | print(r) 143 | -------------------------------------------------------------------------------- /orbitdeterminator/util/read_data.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Reads the positional data set from a .csv file 3 | ''' 4 | 5 | import os 6 | import csv 7 | import pickle 8 | import numpy as np 9 | import json 10 | import sys 11 | import glob 12 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 13 | import kep_determination.positional_observation_reporting as por 14 | 15 | 16 | _SOURCE = "../raw data" 17 | _DESTINATION = "../filtered data" 18 | 19 | def load_data(filename): 20 | ''' 21 | Loads the data in numpy array for further processing in tab delimiter format 22 | 23 | Args: 24 | filename (string): name of the csv file to be parsed 25 | 26 | Returns: 27 | numpy array: array of the orbit positions, each point of the orbit is of the 28 | format (time, x, y, z) 29 | ''' 30 | return np.genfromtxt(filename, delimiter='\t')[1:] 31 | 32 | def save_orbits(source, destination): 33 | ''' 34 | Saves objects returned from load_data 35 | 36 | Args: 37 | source: path to raw csv files. 38 | destination: path where objects need to be saved. 39 | ''' 40 | if os.path.isdir(source): 41 | pass 42 | else: 43 | os.system("mkdir {}".format(destination)) 44 | 45 | for file in os.listdir(source): 46 | if file.endswith('.csv'): 47 | orbit = load_data(source + '/' + str(file)) 48 | pickle.dump(orbit, open(destination + "/%s.p" %file[:-4], "wb")) 49 | 50 | 51 | def detect_json(filename): 52 | # detect json 53 | try: 54 | with open(filename, 'r') as infile: 55 | data = json.load(infile) 56 | file = {"file" : "json"} 57 | except: 58 | file = {"file" : None} 59 | 60 | return file 61 | 62 | 63 | def detect_iod(filename): 64 | if por.check_iod_format(filename) == True: 65 | file = {"file" : "iod"} 66 | else: 67 | file = {"file": None} 68 | 69 | return file 70 | 71 | 72 | def detect_uk(filename): 73 | if por.check_uk_format(filename) == True: 74 | file = {"file" : "uk"} 75 | else: 76 | file = {"file": None} 77 | 78 | return file 79 | 80 | 81 | def detect_csv(filename): 82 | 83 | file = {"file": None} 84 | file1 = file 85 | 86 | try: 87 | linecheck = np.genfromtxt(filename, delimiter='\t')[1] 88 | 89 | try: 90 | if len(linecheck) > 1: 91 | file1 = {"file": "csv", 92 | "delimiter": "\t"} 93 | except: 94 | file1 = {"file": None} 95 | 96 | except: 97 | file1 = {"file": None} 98 | 99 | 100 | 101 | 102 | file2 = file 103 | try: 104 | linecheck = np.genfromtxt(filename, delimiter=';')[1] 105 | 106 | try: 107 | 108 | if len(linecheck) > 1: 109 | file2 = {"file": "csv", 110 | "delimiter": ";"} 111 | 112 | except: 113 | file2 = {"file": None} 114 | 115 | except: 116 | file2 = {"file": None} 117 | 118 | 119 | if file1["file"] is not None: 120 | file = file1 121 | 122 | if file2["file"] is not None: 123 | file = file2 124 | 125 | return file 126 | 127 | 128 | def detect_file_format(filename): 129 | 130 | file = {"file" : None} 131 | 132 | if os.path.exists(filename): 133 | 134 | # loading in all files and check for all 3 formats we currently support 135 | file_json = detect_json(filename) 136 | file_iod = detect_iod(filename) 137 | file_csv = detect_csv(filename) 138 | file_uk = detect_uk(filename) 139 | #file_rde = detect_rde(filename) 140 | 141 | if file_json["file"] == "json": 142 | return file_json 143 | 144 | elif file_iod["file"] == "iod": 145 | return file_iod 146 | 147 | elif file_csv["file"] == "csv": 148 | return file_csv 149 | 150 | elif file_uk["file"] == "uk": 151 | return file_uk 152 | 153 | else: 154 | return file 155 | 156 | 157 | else: 158 | # no file available 159 | return file 160 | 161 | 162 | def get_all_files(path): 163 | # list all files from a folder, or just the one file that was stated. 164 | 165 | files = [] 166 | if os.path.isdir(path): 167 | files = glob.glob((path + os.sep+'*')) 168 | 169 | if os.path.isfile(path): 170 | files = glob.glob((path)) 171 | 172 | return files 173 | 174 | 175 | if __name__ == "__main__": 176 | 177 | #save_orbits(_SOURCE, _DESTINATION) 178 | print("detecting file", detect_file_format("../example_data/orbit.csv")) 179 | print("detecting file", detect_file_format("../example_data/SATOBS-ML-19200716.txt")) 180 | print("detecting file", detect_file_format("../example_data/orbit1.csv")) 181 | print("detecting file", detect_file_format("../example_data/sampleukformatdata.txt")) 182 | -------------------------------------------------------------------------------- /orbitdeterminator/util/rkf5.py: -------------------------------------------------------------------------------- 1 | """Old script documentation and code is kinda bad""" 2 | 3 | from math import * 4 | from decimal import * 5 | import numpy as np 6 | 7 | 8 | def ypol_a(t,y): 9 | 10 | # function which computes the 1x6 vector y_parag (contains velocity and 11 | # acceleration values) by using the state vector y 12 | # keplerian motion to initialize the acceleration vector 13 | 14 | # input 15 | 16 | # y = state vector (y(1),y(2),y(3) = position vector and y(4),y(5),y(6) = velocity vector) 17 | # y(1),y(2),y(3) m and y(4),y(5),y(6) m/s 18 | 19 | # output 20 | 21 | # y_parag = y' vector which contains the velocity and acceleration values 22 | # y_parag(1,2,3) = velocity vector and y_parag(4,5,6) = acceleration vector 23 | # y_parag(1,2,3) = m/s and y_parag(4,5,6) = m/s^2 24 | 25 | mu=398600.4405 26 | y_parag = np.zeros((6,1)) 27 | agrav = np.zeros((3,1)) 28 | 29 | 30 | r2 = y[0,0]*y[0,0] + y[1,0]*y[1,0] + y[2,0]*y[2,0] 31 | r1 = sqrt(r2) 32 | r3 = r2*r1 33 | 34 | for i in range(0,3): 35 | agrav[i,0] = agrav[i,0] -(mu * y[i,0] / r3) 36 | 37 | 38 | y_parag[0,0]=y[3,0] 39 | y_parag[1,0]=y[4,0] 40 | y_parag[2,0]=y[5,0] 41 | y_parag[3,0]=agrav[0,0] 42 | y_parag[4,0]=agrav[1,0] 43 | y_parag[5,0]=agrav[2,0] 44 | return y_parag 45 | 46 | 47 | def rkf5 (ti,tf,h,x,neq=6): 48 | 49 | # RKF45 method 50 | #input 51 | 52 | # deq = name of function which defines the 53 | # system of differential equations 54 | # neq = number of differential equations 55 | # ti = initial simulation time 56 | # tf = final simulation time 57 | # h = initial guess for integration step size 58 | # emin = minimum error we require from the solution 59 | # emax = max error we require from the solution 60 | # x = integration vector at time = ti 61 | 62 | # output 63 | 64 | # x1 = integration vector at time = tf 65 | 66 | ## 67 | yoRKF5 = np.zeros((6,1)) 68 | K1neo = np.zeros((6,1)) 69 | K2neo = np.zeros((6,1)) 70 | K3neo = np.zeros((6,1)) 71 | K4neo = np.zeros((6,1)) 72 | K5neo = np.zeros((6,1)) 73 | K6neo = np.zeros((6,1)) 74 | y1RKF5 = np.zeros((6,1)) 75 | yoRKF5[:]=x[:] 76 | ta=ti 77 | epoch_solution = [] 78 | keep = [] 79 | ## 80 | 81 | while True: 82 | dt=tf-ti 83 | tt=ti+h 84 | 85 | 86 | if (abs(h)>abs(dt)): 87 | h=dt 88 | 89 | ## 90 | for j in range(0,1): 91 | 92 | 93 | #parametroi K gia RKF5 94 | K1neo = h * ypol_a(ti,yoRKF5) 95 | 96 | t2=ti+(1.0/4)*h 97 | yo2neo=yoRKF5+(1/4)*K1neo 98 | K2neo = h * ypol_a(t2,yo2neo) 99 | 100 | t3=ti+(3.0/8)*h 101 | yo3neo=yoRKF5+(3.0/32)*K1neo+(9.0/32)*K2neo 102 | K3neo=h*ypol_a(t3,yo3neo) 103 | 104 | t4=ti+(12.0/13)*h 105 | yo4neo=yoRKF5+(1932.0/2197)*K1neo-(7200.0/2197)*K2neo+(7296.0/2197)*K3neo 106 | K4neo=h*ypol_a(t4,yo4neo) 107 | 108 | t5=ti+h 109 | yo5neo=yoRKF5+(439.0/216)*K1neo-8*K2neo+(3680.0/513)*K3neo-(845.0/4104)*K4neo 110 | K5neo=h*ypol_a(t5,yo5neo) 111 | 112 | t6=ti+(1.0/2)*h 113 | yo6neo=yoRKF5-(8.0/27)*K1neo+2*K2neo-(3544.0/2565)*K3neo+(1859.0/4104)*K4neo-(11.0/40)*K5neo 114 | K6neo=h*ypol_a(t6,yo6neo) 115 | ## 116 | 117 | y1RKF5=yoRKF5+(16.0/135)*K1neo+(6656.0/12825)*K3neo+(28561.0/56430)*K4neo-(9.0/50)*K5neo+(2.0/55)*K6neo 118 | 119 | yoRKF5[:]=y1RKF5[:] 120 | 121 | 122 | epoch_solution.append(y1RKF5) 123 | ti=ti+h 124 | 125 | if (abs(ti - tf) < 0.00000001): 126 | 127 | xrkf5=y1RKF5 128 | final = np.zeros((len(epoch_solution), 6)) 129 | for i in range(0, len(epoch_solution)): 130 | final[i, :] = np.ravel(epoch_solution[i]) 131 | 132 | return xrkf5, final 133 | 134 | 135 | if __name__ == "__main__": 136 | # Starting time 137 | ti = 1.0 138 | # Final time 139 | tf = 1000.0 140 | # Step of the algo, it will give one state vector for every 10sec from ti to tf 141 | h = 10.0 142 | # Initial State vector 143 | x = np.array([[1.51303397e+03],[-2.48429276e+03],[6.46549360e+03],[2.99258730e+00],[-6.15860507e+00],[-3.06500279e+00]]) 144 | 145 | xrkf5, every = rkf5(ti,tf,h,x) 146 | # Every is the state vector from ti to tf for each step 147 | print(every) 148 | -------------------------------------------------------------------------------- /orbitdeterminator/util/rkf78.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Uses Runge Kutta Fehlberg 7(8) numerical integration method to compute the state vector in 3 | a time interval tf 4 | ''' 5 | 6 | from math import * 7 | from decimal import * 8 | import numpy as np 9 | np.set_printoptions(precision=16) 10 | 11 | 12 | def ypol_a(y): 13 | ''' 14 | Computes velocity and acceleration values by using the state vector y and keplerian motion 15 | 16 | Args: 17 | y (numpy array): state vector (position + velocity) 18 | 19 | Returns: 20 | numpy array: derivative of the state vector (velocity + acceleration) 21 | 22 | ''' 23 | mu=398600.4405 24 | y_parag = np.zeros((6,1)) 25 | agrav = np.zeros((3,1)) 26 | 27 | r2 = y[0,0]*y[0,0] + y[1,0]*y[1,0] + y[2,0]*y[2,0] 28 | r1 = sqrt(r2) 29 | r3 = r2*r1 30 | 31 | for i in range(0,3): 32 | agrav[i,0] = agrav[i,0] -(mu * y[i,0] / r3) 33 | 34 | y_parag[0,0]=y[3,0] 35 | y_parag[1,0]=y[4,0] 36 | y_parag[2,0]=y[5,0] 37 | y_parag[3,0]=agrav[0,0] 38 | y_parag[4,0]=agrav[1,0] 39 | y_parag[5,0]=agrav[2,0] 40 | return y_parag 41 | 42 | 43 | def rkf78(neq,ti,tf,h,tetol,x): 44 | ''' 45 | Runge-Kutta-Fehlberg 7[8] method, solve first order system of differential equations 46 | 47 | Args: 48 | neq (int): number of differential equations 49 | ti (float): initial simulation time 50 | tf (float): final simulation time 51 | h (float): initial guess for integration step size 52 | tetol (float): truncation error tolerance [non-dimensional] 53 | x (numpy array): integration vector at time = ti 54 | 55 | Returns: 56 | numpy array: array of state vector at time tf 57 | ''' 58 | 59 | # allocate arrays 60 | 61 | # define integration coefficients 62 | 63 | ch = np.zeros((13,1)) 64 | alph = np.zeros((13,1)) 65 | beta = np.zeros((13, 12)) 66 | 67 | ch[5,0] = 34.0 / 105 68 | ch[6,0] = 9.0 / 35 69 | ch[7,0] = ch[6,0] 70 | ch[8,0] = 9.0 / 280 71 | ch[9,0] = ch[8,0] 72 | ch[11,0] = 41.0 / 840 73 | ch[12,0] = ch[11,0] 74 | 75 | alph[1,0] = 2.0 / 27 76 | alph[2,0] = 1.0 / 9 77 | alph[3,0] = 1.0 / 6 78 | alph[4,0] = 5.0 / 12 79 | alph[5,0] = 0.5 80 | alph[6,0] = 5.0 / 6 81 | alph[7,0] = 1.0 / 6 82 | alph[8,0] = 2.0 / 3 83 | alph[9,0] = 1.0 / 3 84 | alph[10,0] = 1 85 | alph[12,0] = 1 86 | 87 | beta[1,0] = 2.0 / 27 88 | beta[2,0] = 1.0 / 36 89 | beta[3,0] = 1.0 / 24 90 | beta[4,0] = 5.0 / 12 91 | beta[5,0] = 0.05 92 | beta[6,0] = -25.0 / 108 93 | beta[7,0] = 31.0 / 300 94 | beta[8,0] = 2.0 95 | beta[9,0] = -91.0 / 108 96 | beta[10,0] = 2383.0 / 4100 97 | beta[11,0] = 3.0 / 205 98 | beta[12,0] = -1777.0 / 4100 99 | beta[2,1] = 1.0 / 12 100 | beta[3,2] = 1.0 / 8 101 | beta[4,2] = -25.0 / 16 102 | beta[4,3] = -beta[4,2] 103 | beta[5,3] = 0.25 104 | beta[6,3] = 125.0 / 108 105 | beta[8,3] = -53.0 / 6 106 | beta[9,3] = 23.0 / 108 107 | beta[10,3] = -341.0 / 164 108 | beta[12,3] = beta[10,3] 109 | beta[5,4] = 0.2 110 | beta[6,4] = -65.0 / 27 111 | beta[7,4] = 61.0 / 225 112 | beta[8,4] = 704.0 / 45 113 | beta[9,4] = -976.0 / 135 114 | beta[10,4] = 4496.0 / 1025 115 | beta[12,4] = beta[10,4] 116 | beta[6,5] = 125.0 / 54 117 | beta[7,5] = -2.0 / 9 118 | beta[8,5] = -107.0 / 9 119 | beta[9,5] = 311.0 / 54 120 | beta[10,5] = -301.0 / 82 121 | beta[11,5] = -6.0 / 41 122 | beta[12,5] = -289.0 / 82 123 | beta[7,6] = 13.0 / 900 124 | beta[8,6] = 67.0 / 90 125 | beta[9,6] = -19.0 / 60 126 | beta[10,6] = 2133.0 / 4100 127 | beta[11,6] = -3.0 / 205 128 | beta[12,6] = 2193.0 / 4100 129 | beta[8,7] = 3.0 130 | beta[9,7] = 17.0 / 6 131 | beta[10,7] = 45.0 / 82 132 | beta[11,7] = -3.0 / 41 133 | beta[12,7] = 51.0 / 82 134 | beta[9,8] = -1.0 / 12 135 | beta[10,8] = 45.0 / 164 136 | beta[11,8] = 3.0 / 41 137 | beta[12,8] = 33.0 / 164 138 | beta[10,9] = 18.0 / 41 139 | beta[11,9] = 6.0 / 41 140 | beta[12,9] = 12.0 / 41 141 | beta[12,11] = 1.0 142 | 143 | f = np.zeros((neq,13)) 144 | 145 | xdot = np.zeros((neq,1)) 146 | 147 | xwrk = np.zeros((neq, 1)) 148 | 149 | # compute integration "direction" 150 | dt = h 151 | 152 | while True: 153 | 154 | # load "working" time and integration vector 155 | 156 | twrk = ti 157 | xwrk[:,0] = x[:,0] 158 | 159 | # check for last dt 160 | 161 | if abs(dt) > abs(tf - ti): 162 | dt = tf - ti 163 | 164 | # check for end of integration period 165 | 166 | if abs(ti - tf) < 0.00000001: 167 | xout = x 168 | return xout 169 | 170 | xdot = ypol_a(x) 171 | xdot_tra = np.transpose(xdot) 172 | f[:, 0] = xdot_tra 173 | 174 | for k in range(1,13): 175 | 176 | for i in range(0,neq): 177 | 178 | x[i,0] = xwrk[i,0] + dt * sum(beta[k, 0:k] * f[i, 0:k]) 179 | ti = twrk + alph[k,0] * dt 180 | xdot = ypol_a(x) 181 | xdot_tra=np.transpose(xdot) 182 | f[:,k] = xdot_tra 183 | 184 | xerr = tetol 185 | for i in range(0,neq): 186 | f_tra=np.transpose(f) 187 | x[i,0] = xwrk[i,0] + dt * sum(ch[:,0] * f_tra[:,i]) 188 | 189 | # truncation error calculations 190 | 191 | ter = abs((f[i, 0] + f[i, 10] - f[i, 11] - f[i, 12]) * ch[11,0] * dt) 192 | tol = abs(x[i,0]) * tetol + tetol 193 | tconst = ter / tol 194 | 195 | if tconst > xerr: xerr = tconst 196 | ## 197 | # compute new step size 198 | 199 | dt = 0.8 * dt * (1.0 / xerr) ** (1.0 / 8) 200 | 201 | if (xerr > 1): 202 | # reject current step 203 | ti = twrk 204 | x = xwrk 205 | 206 | 207 | if __name__ == "__main__": 208 | neq = 6 209 | ti = 1.0 210 | tf = 100.0 211 | h = 0.1 212 | tetol = 1e-04 213 | x = np.array([[1.51303397e+03],[-2.48429276e+03],[6.46549360e+03],[2.99258730e+00],[-6.15860507e+00],[-3.06500279e+00]]) 214 | 215 | xout = rkf78(neq, ti, tf, h, tetol, x) 216 | print(sqrt(xout[0]**2+xout[1]**2+xout[2]**2)) 217 | print(xout) 218 | 219 | -------------------------------------------------------------------------------- /orbitdeterminator/util/state_kep.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Takes a state vector (x, y, z, vx, vy, vz) where v is the velocity of the satellite 3 | and transforms it into a set of keplerian elements (a, e, i, ω, Ω, v) 4 | 5 | ''' 6 | 7 | import numpy as np 8 | import math 9 | 10 | def state_kep(r, v): 11 | ''' 12 | Converts state vector to orbital elements. 13 | 14 | Args: 15 | r (numpy array): position vector 16 | v (numpy array): velocity vector 17 | 18 | Returns: 19 | numpy array: array of the computed keplerian elements 20 | kep(0): semimajor axis (kilometers) 21 | kep(1): orbital eccentricity (non-dimensional) 22 | (0 <= eccentricity < 1) 23 | kep(2): orbital inclination (degrees) 24 | kep(3): argument of perigee (degress) 25 | kep(4): right ascension of ascending node (degrees) 26 | kep(5): true anomaly (degrees) 27 | ''' 28 | 29 | mu = 398600.4405 30 | mag_r = np.sqrt(r.dot(r)) 31 | mag_v = np.sqrt(v.dot(v)) 32 | 33 | h = np.cross(r, v) 34 | mag_h = np.sqrt(h.dot(h)) 35 | 36 | e = ((np.cross(v, h)) / mu) - (r / mag_r) 37 | mag_e = np.sqrt(e.dot(e)) 38 | 39 | n = np.array([-h[1], h[0], 0]) 40 | mag_n = np.sqrt(n.dot(n)) 41 | 42 | true_anom = math.acos(np.clip(np.dot(e,r)/(mag_r * mag_e), -1, 1)) 43 | if np.dot(r, v) < 0: 44 | true_anom = 2 * math.pi - true_anom 45 | true_anom = math.degrees(true_anom) 46 | 47 | i = math.acos(np.clip(h[2] / mag_h, -1, 1)) 48 | i = math.degrees(i) 49 | 50 | ecc = mag_e 51 | 52 | raan = math.acos(np.clip(n[0] / mag_n, -1, 1)) 53 | if n[1] < 0: 54 | raan = 2 * math.pi - raan 55 | raan = math.degrees(raan) 56 | 57 | per = math.acos(np.clip(np.dot(n, e) / (mag_n * mag_e), -1, 1)) 58 | if e[2] < 0: 59 | per = 2 * math.pi - per 60 | per = math.degrees(per) 61 | 62 | a = 1 / ((2 / mag_r) - (mag_v**2 / mu)) 63 | 64 | if i >= 360.0: 65 | i = i - 360 66 | if raan >= 360.0: 67 | raan = raan - 360 68 | if per >= 360.0: 69 | per = per - 360 70 | 71 | kep = np.zeros(6) 72 | kep[0] = a 73 | kep[1] = ecc 74 | kep[2] = i 75 | kep[3] = per 76 | kep[4] = raan 77 | kep[5] = true_anom 78 | return kep 79 | 80 | 81 | if __name__ == "__main__": 82 | r = np.array([5.0756899358316559e+03, -4.5590381308371752e+03, 1.9322228177731663e+03]) 83 | v = np.array([1.3360847905126974e+00, -1.5698574946888049e+00, -7.2117328822023676e+00]) 84 | 85 | kep = state_kep(r, v) 86 | print(kep) 87 | -------------------------------------------------------------------------------- /orbitdeterminator/util/teme_to_ecef.py: -------------------------------------------------------------------------------- 1 | """Converts coordinates in TEME frame to ECEF frame.""" 2 | 3 | from datetime import datetime, timezone 4 | import numpy as np 5 | 6 | def conv_to_ecef(coords): 7 | """Converts coordinates in TEME frame to ECEF frame. 8 | 9 | Args: 10 | coords(nx4 numpy array): list of coordinates in the format [t,x,y,z] 11 | 12 | Returns: 13 | nx4 numpy array: list of coordinates in the format 14 | [t, latitude, longitude, altitude] 15 | 16 | Note that these coordinates are with respect to the 17 | surface of the Earth. Latitude, longitude are in degrees. 18 | """ 19 | 20 | t = coords[:,0] 21 | x = coords[:,1] 22 | y = coords[:,2] 23 | z = coords[:,3] 24 | 25 | alt = (x**2+y**2+z**2)**0.5 26 | lat = np.degrees(np.arcsin(z/alt)) 27 | lng = np.degrees(np.arctan2(y,x)%(2*np.pi)) 28 | 29 | midnight = datetime.fromtimestamp(t[0],tz=timezone.utc) 30 | midnight = midnight.replace(hour=0,minute=0,second=0,microsecond=0) 31 | t_mid = midnight.timestamp() 32 | 33 | J2000 = 946728000 34 | Tu = (t_mid-J2000)/86400/36525 35 | tg0h = 24110.54841 + 8640184.812866*Tu + 0.093104*Tu**2 - 6.2e-6*Tu**3 36 | we = 1.00273790935 37 | tgt = tg0h + we*(t-t_mid) 38 | era = (tgt%86400)*360/86400 39 | lng = lng-era 40 | return np.column_stack((t,lat,lng,alt)) 41 | 42 | if __name__ == "__main__": 43 | ecef_coords = conv_to_ecef(np.array([[1521562500,768.281,5835.68,2438.076],[1521562500,768.281,5835.68,2438.076],[1521562500,768.281,5835.68,2438.076]])) 44 | print(ecef_coords) 45 | -------------------------------------------------------------------------------- /orbitdeterminator/util/tle_kep_state.py: -------------------------------------------------------------------------------- 1 | """Old script kinda bad coding practises. 2 | Important! Output state vector in km""" 3 | 4 | import numpy as np 5 | from math import * 6 | 7 | 8 | def Mtov(M, e): 9 | # Computes true anomaly v from a given mean anomaly M and eccentricity e by using Newton-Raphson method 10 | 11 | # input 12 | 13 | # M = mean anomaly (degrees) 14 | # e = eccentricity (number) 15 | 16 | # output 17 | 18 | # v = true anomaly (degrees) 19 | i = 1 20 | Eo = 100 21 | while True: 22 | E = Eo - ((Eo - e * sin(Eo) - M) / (1 - e * cos(Eo))) 23 | Eo = E 24 | i = i + 1 25 | if i == 100: break 26 | 27 | v_pre = (cos(E) - e) / (1 - e * cos(E)) 28 | v = acos(v_pre) 29 | v = degrees(v) 30 | return v 31 | 32 | 33 | def Kep_state(kep): 34 | # this function uses the keplerian elements to compute the position and velocity vector 35 | 36 | # input 37 | 38 | # kep is a 1x6 matrix which contains the following variables 39 | # kep(0)=inclination (degrees) 40 | # kep(1)=right ascension of the ascending node (degrees) 41 | # kep(2)=eccentricity (number) 42 | # kep(3)=argument of perigee (degrees) 43 | # kep(4)=mean anomaly (degrees) 44 | # kep(5)=mean motion (revs per day) 45 | 46 | # output 47 | 48 | # r = 1x6 matrix which contains the position and velocity vector 49 | # r(0),r(1),r(2) = position vector (rx,ry,rz) m 50 | # r(3),r(4),r(5) = velocity vector (vx,vy,vz) m/s 51 | 52 | r = np.zeros((6, 1)) 53 | mu = 398600.4405 54 | 55 | # unload orbital elements array 56 | 57 | sma_pre = (398600.4405 * (86400 ** 2)) / ((kep[5, 0] ** 2) * 4 * (pi ** 2)); 58 | sma = sma_pre ** (1.0 / 3.0) # sma is semi major axis, we use mean motion (kep(6)) to compute this 59 | ecc = kep[2, 0] # eccentricity 60 | inc = kep[0, 0] # inclination 61 | argper = kep[3, 0] # argument of perigee 62 | raan = kep[1, 0]; # right ascension of the ascending node 63 | tanom = Mtov(kep[4, 0], ecc) # we use mean anomaly(kep(5)) and the function Mtov to compute true anomaly (tanom) 64 | 65 | tanom = radians(tanom) 66 | slr = sma * (1 - ecc * ecc) 67 | rm = slr / (1 + ecc * cos(tanom)) 68 | tanom = degrees(tanom) 69 | 70 | arglat = argper + tanom; # argument of latitude 71 | 72 | arglat = radians(arglat) 73 | sarglat = sin(arglat) 74 | carglat = cos(arglat) 75 | arglat = degrees(arglat) 76 | 77 | argper = radians(argper) 78 | c4 = sqrt(mu / slr) 79 | c5 = ecc * cos(argper) + carglat 80 | c6 = ecc * sin(argper) + sarglat 81 | argper = degrees(argper) 82 | 83 | inc = radians(inc) 84 | sinc = sin(inc) 85 | cinc = cos(inc) 86 | inc = degrees(inc) 87 | 88 | raan = radians(raan) 89 | sraan = sin(raan) 90 | craan = cos(raan) 91 | raan = degrees(raan) 92 | 93 | # position vector 94 | 95 | r[0, 0] = rm * (craan * carglat - sraan * cinc * sarglat) 96 | r[1, 0] = rm * (sraan * carglat + cinc * sarglat * craan) 97 | r[2, 0] = rm * sinc * sarglat 98 | 99 | # velocity vector 100 | 101 | r[3, 0] = -c4 * (craan * c6 + sraan * cinc * c5) 102 | r[4, 0] = -c4 * (sraan * c6 - craan * cinc * c5) 103 | r[5, 0] = c4 * c5 * sinc 104 | 105 | return r 106 | 107 | 108 | if __name__ == "__main__": 109 | 110 | kep = np.array([[92.0301], [121.6730], [0.0009479], [50.1105], [310.0935], [14.5217343233]]) 111 | 112 | r = Kep_state(kep) 113 | print(r) 114 | 115 | 116 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | astropy >= 3.0.3 2 | emcee 3 | inquirer 4 | matplotlib >= 2.1.2 5 | numpy >= 1.17.0 6 | poliastro >= 0.9.1 7 | py >= 1.5.2 8 | pytest >= 3.4.0 9 | requests 10 | scipy >= 1.0.0 11 | sgp4 >= 1.4 12 | skyfield 13 | tqdm 14 | vpython 15 | https://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-1.0.12.tar.gz 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import * 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | with open("requirements.txt", "r") as fh: 7 | requirements = fh.readlines() 8 | 9 | setup( 10 | name="orbitdeterminator", 11 | packages=find_packages(), 12 | version="1.0", 13 | license="MIT", 14 | author="aerospaceresearch.net community", 15 | author_email="orbitdeterminator@aerospaceresearch.net", 16 | url="http://orbit-determinator.readthedocs.io/en/latest/", 17 | description="__orbitdeterminator__ is a package written in python3 for determining orbit of satellites based " \ 18 | "on positional data. Various filtering and determination algorithms are available for satellite " \ 19 | "operators to choose from. ", 20 | long_description=long_description, 21 | long_description_content_type="text/markdown", 22 | install_requires=[req for req in requirements if req[:2] != "# "and req[:4] != "http"], 23 | dependency_links=[req for req in requirements if req[:4] != "http"], 24 | classifiers=[ 25 | "Programming Language :: Python :: 3", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | ], 29 | python_requires='>=3.5', 30 | ) 31 | --------------------------------------------------------------------------------