├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── CODEOWNERS ├── COPYING ├── Makefile ├── README.md ├── bin └── build-manylinux-wheel.sh ├── licensing ├── CHANGES ├── COPYING ├── CREDITS ├── HISTORY ├── NEWS └── README ├── predict.c ├── predict.py ├── setup.py ├── test_predict.py ├── test_predict_above.py └── tox.ini /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '**' 9 | pull_request: {} 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python 3.11 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.11" 20 | - name: Check style with `black` 21 | uses: psf/black@stable 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install flake8 pytest 26 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 27 | - name: Lint with flake8 28 | run: | 29 | # Stop the build if there are Python syntax errors or undefined names 30 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 31 | # `exit-zero` treats all errors as warnings. 32 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics 33 | 34 | build: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Build artifacts 39 | run: make build 40 | - uses: actions/upload-artifact@v4 41 | with: 42 | name: dist 43 | path: ./dist/* 44 | 45 | test: 46 | needs: build 47 | runs-on: ${{ matrix.os }} 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 52 | os: [ubuntu-latest] 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: actions/setup-python@v5 56 | with: 57 | python-version: ${{ matrix.python-version }} 58 | - name: Download compiled wheels 59 | uses: actions/download-artifact@v4 60 | with: 61 | name: dist 62 | path: dist 63 | - name: Install test dependencies 64 | run: | 65 | python -m pip install --upgrade pip 66 | pip install tox tox-gh-actions pytest 67 | - name: Run tests 68 | run: tox 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheelhouse/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | .static_storage/ 57 | .media/ 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | .makerc 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | 108 | .DS_Store 109 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jerematt @mattmolo 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | Appendix: How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) 19yy name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Library General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell python3 ./setup.py --version) 2 | NAME := $(shell python3 ./setup.py --name) 3 | ARCH ?= linux_x86_64 4 | src := predict.py predict.c setup.py 5 | sdist := dist/$(NAME)-$(VERSION).tar.gz 6 | wheels := \ 7 | dist/$(NAME)-$(VERSION)-cp27-cp27m-$(ARCH).whl \ 8 | dist/$(NAME)-$(VERSION)-cp27-cp27mu-$(ARCH).whl \ 9 | dist/$(NAME)-$(VERSION)-cp37-cp37m-$(ARCH).whl \ 10 | dist/$(NAME)-$(VERSION)-cp38-cp38-$(ARCH).whl \ 11 | dist/$(NAME)-$(VERSION)-cp39-cp39-$(ARCH).whl \ 12 | dist/$(NAME)-$(VERSION)-cp310-cp310-$(ARCH).whl \ 13 | dist/$(NAME)-$(VERSION)-cp311-cp311-$(ARCH).whl 14 | 15 | .PHONY: help 16 | help: 17 | @echo "Targets:" 18 | @echo " clean: Removes distribution folders and artifacts from building" 19 | @echo " build: Builds source and wheel distributions" 20 | @echo " upload: Uploads built source and wheel distributions to repository" 21 | @echo " Requires env vars REPO, USER, PASSWORD" 22 | 23 | .PHONY: clean 24 | clean: 25 | rm -rf wheelhouse dist/ build/ __pycache__/ *.egg-info/ tletools/*.pyc venv .pytest_cache/ 26 | 27 | .PHONY: build 28 | build: sdist manylinux-wheels 29 | 30 | .PHONY: manylinux-wheels 31 | manylinux-wheels: $(wheels) 32 | 33 | $(wheels): $(src) 34 | docker run --user $(shell id -u):$(shell id -g) -v $(shell pwd):/io \ 35 | quay.io/pypa/manylinux1_x86_64:latest \ 36 | /io/bin/build-manylinux-wheel.sh 27 37 | docker run --user $(shell id -u):$(shell id -g) -v $(shell pwd):/io \ 38 | quay.io/pypa/manylinux_2_28_x86_64:latest \ 39 | /io/bin/build-manylinux-wheel.sh 37 38 39 310 311 40 | 41 | .PHONY: sdist 42 | sdist: $(sdist) 43 | 44 | $(sdist): $(src) 45 | python3 setup.py sdist 46 | 47 | .PHONY: install 48 | install: build 49 | python3 setup.py install 50 | 51 | .PHONY: test 52 | test: install 53 | pytest 54 | 55 | .PHONY: upload 56 | upload: build 57 | twine upload wheelhouse/* $(sdist) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ci](https://github.com/nsat/pypredict/actions/workflows/python-app.yml/badge.svg)](https://github.com/nsat/pypredict/actions/workflows/python-app.yml) 2 | 3 | PyPredict 4 | ======= 5 | 6 | >NOTE: To preserve compatibility with `predict`, pypredict uses __north__ latitude and __west__ longitude for terrestrial coordinates. 7 | 8 | Do you want accurate and time-tested satellite tracking and pass prediction in a convenient python wrapper? 9 | You're in the right place. 10 | 11 | PyPredict is a C Python extension directly adapted from the ubiquitous [predict](http://www.qsl.net/kd2bd/predict.html) satellite tracking command line application. 12 | Originally written for the commodore 64, predict has a proven pedigree; We just aim to provide a convenient API. 13 | PyPredict is a port of the predict codebase and should yield identical results. 14 | 15 | If you think you've found an error in `pypredict`, please include output from `predict` on same inputs to the bug report. 16 | If you think you've found a bug in predict, please report and we'll coordinate with upstream. 17 | 18 | ### Installation 19 | 20 | ```bash 21 | sudo apt-get install python-dev 22 | sudo python setup.py install 23 | ``` 24 | 25 | ## Usage 26 | 27 | #### Observe a satellite (relative to a position on earth) 28 | 29 | ```python 30 | import predict 31 | tle = """0 LEMUR 1 32 | 1 40044U 14033AL 15013.74135905 .00002013 00000-0 31503-3 0 6119 33 | 2 40044 097.9584 269.2923 0059425 258.2447 101.2095 14.72707190 30443""" 34 | qth = (37.771034, 122.413815, 7) # lat (N), long (W), alt (meters) 35 | predict.observe(tle, qth) # optional time argument defaults to time.time() 36 | # => {'altitude': 676.8782276657903, 37 | # 'azimuth': 96.04762045174824, 38 | # 'beta_angle': -27.92735429908726, 39 | # 'decayed': 0, 40 | # 'doppler': 1259.6041017128405, 41 | # 'eci_obs_x': -2438.227652191655, 42 | # 'eci_obs_y': -4420.154476060397, 43 | # 'eci_obs_z': 3885.390601342013, 44 | # 'eci_sun_x': 148633398.020844, 45 | # 'eci_sun_y': -7451536.44122029, 46 | # 'eci_sun_z': -3229999.50056359, 47 | # 'eci_vx': 0.20076213530665032, 48 | # 'eci_vy': -1.3282146055077213, 49 | # 'eci_vz': 7.377067234096598, 50 | # 'eci_x': 6045.827328897242, 51 | # 'eci_y': -3540.5885778261277, 52 | # 'eci_z': -825.4065096776636, 53 | # 'eclipse_depth': -87.61858291647795, 54 | # 'elevation': -43.711904591801726, 55 | # 'epoch': 1521290038.347793, 56 | # 'footprint': 5633.548906707907, 57 | # 'geostationary': 0, 58 | # 'has_aos': 1, 59 | # 'latitude': -6.759563817939698, 60 | # 'longitude': 326.1137007912563, 61 | # 'name': '0 LEMUR 1', 62 | # 'norad_id': 40044, 63 | # 'orbit': 20532, 64 | # 'orbital_model': 'SGP4', 65 | # 'orbital_phase': 145.3256815318047, 66 | # 'orbital_velocity': 26994.138671706416, 67 | # 'slant_range': 9743.943478523843, 68 | # 'sunlit': 1, 69 | # 'visibility': 'D' 70 | # } 71 | ``` 72 | 73 | #### Show upcoming transits of satellite over ground station 74 | 75 | ```python 76 | # start and stop transit times as UNIX timestamp 77 | transit_start = 1680775200 78 | transit_stop = 1681034400 79 | 80 | p = predict.transits(tle, qth, transit_start, transit_stop) 81 | 82 | print("Start of Transit\tTransit Duration (s)\tPeak Elevation") 83 | for transit in p: 84 | print(f"{transit.start}\t{transit.duration()}\t{transit.peak()['elevation']}") 85 | ``` 86 | 87 | 88 | #### Modeling an entire constellation 89 | 90 | Generating transits for a lot of satellites over a lot of ground stations can be slow. 91 | Luckily, generating transits for each satellite-groundstation pair can be parallelized for a big speed-up. 92 | 93 | ```python 94 | import itertools 95 | from multiprocessing.pool import Pool 96 | import time 97 | 98 | import predict 99 | import requests 100 | 101 | # Define a function that returns arguments for all the transits() calls you want to make 102 | def _transits_call_arguments(): 103 | now = time.time() 104 | tle = requests.get('http://tle.spire.com/25544').text.rstrip() 105 | for latitude in range(-90, 91, 15): 106 | for longitude in range(-180, 181, 15): 107 | qth = (latitude, longitude, 0) 108 | yield {'tle': tle, 'qth': qth, 'ending_before': now+60*60*24*7} 109 | 110 | # Define a function that calls the transit function on a set of arguments and does per-transit processing 111 | def _transits_call_fx(kwargs): 112 | try: 113 | transits = list(predict.transits(**kwargs)) 114 | return [t.above(10) for t in transits] 115 | except predict.PredictException: 116 | pass 117 | 118 | # Map the transit() caller across all the arguments you want, then flatten results into a single list 119 | pool = Pool(processes=10) 120 | array_of_results = pool.map(_transits_call_fx, _transits_call_arguments()) 121 | flattened_results = list(itertools.chain.from_iterable(filter(None, array_of_results))) 122 | transits = flattened_results 123 | ``` 124 | 125 | NOTE: If precise accuracy isn't necessary (for modeling purposes, for example) setting the tolerance argument 126 | to the `above` call to a larger value, say 1 degree, can provide a significant performance boost. 127 | 128 | #### Call predict analogs directly 129 | 130 | ```python 131 | predict.quick_find(tle.split('\n'), time.time(), (37.7727, 122.407, 25)) 132 | predict.quick_predict(tle.split('\n'), time.time(), (37.7727, 122.407, 25)) 133 | ``` 134 | 135 | ## API 136 |
137 | observe(tle, qth[, at=None])  
138 |     Return an observation of a satellite relative to a groundstation.
139 |     qth groundstation coordinates as (lat(N),long(W),alt(m))
140 |     If at is not defined, defaults to current time (time.time())
141 |     Returns an "observation" or dictionary containing:  
142 |         altitude _ altitude of satellite in kilometers
143 |         azimuth - azimuth of satellite in degrees from perspective of groundstation.
144 |         beta_angle
145 |         decayed - 1 if satellite has decayed out of orbit, 0 otherwise.
146 |         doppler - doppler shift between groundstation and satellite.
147 |         eci_obs_x
148 |         eci_obs_y
149 |         eci_obs_z
150 |         eci_sun_x
151 |         eci_sun_y
152 |         eci_sun_z
153 |         eci_vx
154 |         eci_vy
155 |         eci_vz
156 |         eci_x
157 |         eci_y
158 |         eci_z
159 |         eclipse_depth
160 |         elevation - elevation of satellite in degrees from perspective of groundstation.
161 |         epoch - time of observation in seconds (unix epoch)
162 |         footprint
163 |         geostationary - 1 if satellite is determined to be geostationary, 0 otherwise.
164 |         has_aos - 1 if the satellite will eventually be visible from the groundstation
165 |         latitude - north latitude of point on earth directly under satellite.
166 |         longitude - west longitude of point on earth directly under satellite.
167 |         name - name of satellite from first line of TLE.
168 |         norad_id - NORAD id of satellite.
169 |         orbit
170 |         orbital_phase
171 |         orbital_model
172 |         orbital_velocity
173 |         slant_range - distance to satellite from groundstation in meters.
174 |         sunlit - 1 if satellite is in sunlight, 0 otherwise.
175 |         visibility
176 | transits(tle, qth[, ending_after=None][, ending_before=None])  
177 |     Returns iterator of Transit objects representing passes of tle over qth.  
178 |     If ending_after is not defined, defaults to current time  
179 |     If ending_before is not defined, the iterator will yield until calculation failure.
180 | 
181 | >NOTE: We yield passes based on their end time. This means we'll yield currently active passes in the two-argument invocation form, but their start times will be in the past. 182 | 183 |
184 | Transit(tle, qth, start, end)  
185 |     Utility class representing a pass of a satellite over a groundstation.
186 |     Instantiation parameters are parsed and made available as fields.
187 |     duration()  
188 |         Returns length of transit in seconds
189 |     peak(epsilon=0.1)  
190 |         Returns epoch time where transit reaches maximum elevation (within ~epsilon)
191 |     at(timestamp)  
192 |         Returns observation during transit via quick_find(tle, timestamp, qth)
193 |     aboveb(elevation, tolerance)
194 |         Returns portion of transit above elevation. If the entire transit is below the target elevation, both
195 |         endpoints will be set to the peak and the duration will be zero. If a portion of the transit is above
196 |         the elevation target, the endpoints will be between elevation and elevation + tolerance (unless
197 |         endpoint is already above elevation, in which case it will be unchanged)
198 | quick_find(tle[, time[, (lat, long, alt)]])  
199 |     time defaults to current time   
200 |     (lat, long, alt) defaults to values in ~/.predict/predict.qth  
201 |     Returns observation dictionary equivalent to observe(tle, time, (lat, long, alt))
202 | quick_predict(tle[, time[, (lat, long, alt)]])  
203 |         Returns an array of observations for the next pass as calculated by predict.
204 |         Each observation is identical to that returned by quick_find.
205 | 
206 | -------------------------------------------------------------------------------- /bin/build-manylinux-wheel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | PYTHON_VERSIONS=$@ 8 | 9 | if [[ -z "${PYTHON_VERSIONS}" ]] ; then 10 | echo "Usage: $(basename $0) ..." >&2 11 | echo " A version is the two numbers in the major.minor python that you want to build" >&2 12 | echo " Example: for python 2.7 and 3.8 both" >&2 13 | echo "" >&2 14 | echo " $ $(basename $0) 27 38" >&2 15 | exit 1 16 | fi 17 | 18 | function get-variants() { 19 | local version="$1" 20 | 21 | if [[ "${version}" = '38' || "${version}" = '39' || "${version}" = '310' || "${version}" = '311' ]] ; then 22 | echo / 23 | elif [[ "${version}" = '27' ]] ; then 24 | echo m mu 25 | else 26 | echo m 27 | fi 28 | } 29 | 30 | cd /io 31 | mkdir -p dist 32 | 33 | for version in ${PYTHON_VERSIONS} ; do 34 | for variant in $(get-variants "${version}") ; do 35 | "/opt/python/cp${version}-cp${version}${variant}/bin/pip" --no-cache-dir wheel /io -w dist/ 36 | done 37 | 38 | /usr/local/bin/auditwheel repair dist/*cp${version}-cp* 39 | done 40 | -------------------------------------------------------------------------------- /licensing/CHANGES: -------------------------------------------------------------------------------- 1 | Release 2.2.3: 2 | By John A. Magliacane (15-Apr-2006): 3 | 4 | * PREDICT now handles -u, -t, and -q command-line options 5 | (hopefully) better than before. When these options are used 6 | and no default TLE and QTH files are found under the user's 7 | home directory, PREDICT is no longer placed into "new user" 8 | mode. This should ease the use of PREDICT in non-interactive 9 | applications (in automated scripts, etc.) where a $HOME 10 | environment is lacking or not desired. Only if a user 11 | has no QTH and TLE files under their home directory, and 12 | has passed no -q and -t arguments to PREDICT will that 13 | user now be considered a "new user". 14 | 15 | * New command-line options were added to PREDICT. The -east 16 | option causes PREDICT to display longitudes in degrees east 17 | rather than in degrees west. The -south option displays 18 | latitudes in degrees south rather than in degrees north. 19 | -west and -north options, which are the normal defaults, 20 | are also included for completeness. (Tnx: Takeshi-Okazaki, 21 | MTSAT-1R Chief Aeronautical Mission Engineer) 22 | 23 | * RS-232 output in PREDICT and Moontracker has been improved 24 | to prevent the transmission of extraneous bytes past the 25 | end-of-line characters. (Tnx: SP7HAA) 26 | 27 | * Since the bzero() function is being deprecated, all bzero() 28 | function calls were replaced by memset() in all affected code. 29 | 30 | * The accuracy of PREDICT's real-time clock displays have been 31 | improved. 32 | 33 | * A problem was corrected where the GET_SAT_POS socket command 34 | would return results as if the GET_SAT command were also 35 | sent to PREDICT. (Tnx: David Hutchinson, G8SQH) 36 | 37 | * PREDICT now returns a response ("Huh?") to clients that send 38 | unrecognized commands via the UDP socket interface instead 39 | of sending nothing. 40 | 41 | * The GET_DOPPLER socket command now returns Doppler shift 42 | information even if the satellite in question is below the 43 | local horizon. 44 | 45 | * A keyboard interactive socket interface demonstration program 46 | was added ("demo-i" under clients/samples). 47 | 48 | * "earthtrack" was updated to handle changes in xplanet version 49 | 1.0 and later. 50 | 51 | * A small bug in the orbital prediction [P] and visual calendar [V] 52 | modes of PREDICT was fixed. Before the fix, the sunlight/visibility 53 | symbol (*, +) displayed in these modes would sometimes list the 54 | point of LOS as a '*' when it should have been a '+' at the 55 | conclusion of a visible pass. 56 | 57 | * Array overflow conditions in predict.c's Print() and Daynum2String() 58 | functions were identified and fixed. 59 | 60 | * Several very minor coding changes were made to allow warning-free 61 | compilation under GCC 4.x.x. 62 | 63 | ---------------------------------------------------------------------------- 64 | 65 | Release 2.2.2: 66 | By John A. Magliacane (14-Mar-2003): 67 | 68 | * PREDICT's main menu was expanded to handle the additional program 69 | options. The main menu also now displays the text "Server Mode" 70 | when the program is invoked in socket server mode. 71 | 72 | * Orbital phase, eclipse depth, and squint angle data has been appended 73 | to the information returned to UDP client applications in response 74 | to the GET_SAT command. 75 | 76 | * The Moon's declination, Greenwich hour angle, and right ascension 77 | have been appended to the information returned to UDP clients in 78 | response to the GET_MOON command. 79 | 80 | * The Sun's declination, Greenwich hour angle, and right ascension 81 | have been appended to the information returned to UDP clients in 82 | response to the GET_SUN command. 83 | 84 | * PREDICT now returns orbital data in NASA Two-Line format to UDP 85 | clients in response to the GET_TLE command. 86 | 87 | * Added a new UDP socket command (GET_MODE) to return PREDICT's 88 | current tracking mode (single, multi, none) to networked clients. 89 | If PREDICT is operating in Single Satellite tracking mode, then the 90 | name of the satellite currently being tracked is returned by GET_MODE. 91 | 92 | * Fixed a bug that caused GET_SAT requests to fail after GET_DOPPLER 93 | requests were issued. (Tnx: KF6RFX) 94 | 95 | * Fixed a bug in "earthtrack" where latitude and longitude options sent 96 | to "xplanet" were reversed under certain circumstances. (Tnx: ZL2BSJ) 97 | 98 | * In Main Menu option [G] (Edit Ground Station Information), ground 99 | station latitude and longitude may now be entered in degree, minute, 100 | second (DMS) format in addition to decimal degrees. 101 | 102 | * Moon tracking code replaced with a more precise algorithm based 103 | on Meeus' method. 104 | 105 | * Added a lunar prediction mode that predicts passes of the Moon. 106 | 107 | * Added a solar prediction mode that predicts passes of the Sun. 108 | 109 | * Added "MoonTracker", a small background utility that directs 110 | an AZ/EL antenna array toward the position of the Moon. 111 | 112 | * The "vocalizer" feature was greatly improved. Sound samples are 113 | now read from ubiquitious .wav files, making it easier for users 114 | to create samples of their own, even in languages other than English. 115 | PREDICT now articulates numeric values in their entirety (ie: 116 | "three hundred forty nine") instead of articulating each digit 117 | individually ("three four nine") as was done previously. Alarms 118 | and additional announcements were added to alert a visual observer 119 | when a spacecraft is visible, when it enters into sunlight, and 120 | when it enters into eclipse. 121 | 122 | * Added "fodtrack", written by Luc Langermann, LX1GT. This utility 123 | permits AZ/EL antenna control using the Fodtrack hardware interface 124 | via the parallel port. 125 | 126 | * Several other coding changes were made. Linux-specific code was 127 | removed in an effort to allow easier compilation under FreeBSD, 128 | MacOS X, and other Unixes in general. 129 | 130 | * Program documentation was updated to reflect the changes and new 131 | features available in this version of PREDICT. 132 | 133 | ---------------------------------------------------------------------------- 134 | 135 | Release 2.2.1: 136 | By John A. Magliacane (14-Oct-2002): 137 | 138 | * Fixed a problem that caused PREDICT to display the last satellite 139 | twice in listings when less than the maximum number of satellites 140 | existed in predict.tle. 141 | 142 | * Fixed a problem that caused a Doppler shift of zero to be 143 | reported by PREDICT via the UDP interface in MultiTracking mode. 144 | 145 | * Several other minor "tweaks" were made. 146 | 147 | ---------------------------------------------------------------------------- 148 | 149 | Release 2.2.0: (a.k.a. The Columbus Day Release) 150 | By John A. Magliacane (12-Oct-2002): 151 | 152 | * PREDICT's satellite tracking algorithm was replaced by a more precise 153 | model based on a translation into 'C' of Dr. T.S. Kelso's SGP4/SDP4 154 | FORTRAN and Pascal routines by Neoklis Kyriazis, 5B4AZ, and made available 155 | under the GNU General Public License through his "satcom-08" program. 156 | 157 | * PREDICT now imports, exports, and permits editing of TLE element set 158 | numbers, International Designators, and Bstar and nddot/6 drag terms 159 | in addition to all the remaining orbital data found in a two-line 160 | element set. 161 | 162 | * Earthtrack was modified so that when multi-tracking satellites, 163 | range circles are displayed 5 minutes prior to AOS through LOS. 164 | 165 | * Maximum allowable callsign length was increased to 15 characters to 166 | permit groundstation names to be used in place of 6 character callsigns. 167 | 168 | * Vocalizer will no longer send audio to the soundcard if the soundcard 169 | cannot be set to the proper sampling rate and word size. 170 | 171 | * The Single Tracking mode was completely redesigned to allow interactive 172 | selection of satellite transponder, provide real-time Doppler-compensated 173 | uplink and downlink frequency information, and support for a transponder 174 | database. Spacecraft antenna squint angle, propagation delay, echo, 175 | and satellite eclipse depth information are also now available. See 176 | the NEWS file for additional information. 177 | 178 | * Orbital predictions now report squint angles instead of orbit 179 | numbers if ALAT/ALON data is available in the transponder database. 180 | 181 | * A Visual BASIC Windows-based client application has been contributed 182 | by Steve Fraser, VK5ASF. Steve also contributed some small coding 183 | changes that allow PREDICT to be successfully invoked by rc.local 184 | upon startup. 185 | 186 | * Bent Bagger, OZ6BL, contributed a small change to get_response() in 187 | the client demo.c program. The modification recognizes the absence 188 | of a PREDICT server, and displays an appropriate error message. 189 | The previous method set an alarm and waited for a timeout to occur. 190 | 191 | * A bug in the configure script was fixed. 192 | 193 | * Lots of other small coding changes were made. 194 | 195 | ---------------------------------------------------------------------------- 196 | 197 | Release 2.1.6: (a.k.a. The 11th Anniversary Release) 198 | By John A. Magliacane (08-Jul-2002): 199 | 200 | * Buffering added to I/O functions in the vocalizer utility 201 | for better efficiency and less CPU load. 202 | 203 | * Fixed errors in main() (predict.c) that prevented the 204 | vocalizer utility from ever being invoked by PREDICT. 205 | 206 | * Modified the earthtrack utility for compatibility with 207 | versions of xplanet more recent than 0.82. 208 | 209 | * Serial port configuration for antenna rotator interface control 210 | is now automatically set within PREDICT to the EasyComm 2 protocol 211 | standard (9600 bps, 8N1, no handshaking) when the -a or -a1 212 | command-line options are invoked. 213 | 214 | * -a1 command-line option added. It works like the -a option, except 215 | it sends tracking data to the specified serial port at least once 216 | per second, whether the data has changed since the last transmission 217 | or not. These "keepalives" are required for interfaces such as the 218 | ones by Suding Associates, Inc. that throttle back to manual mode if 219 | updates aren't received on a regular and frequent basis. 220 | 221 | * "gsat" by Xavier Crehueras, EB3CZS, has been updated to version 1.0.0. 222 | The new version adds Doppler control for beacon frequencies, support 223 | for a satellite frequency database, and automatic frequency selection. 224 | In addition, an azimuth/elevation graphic window is now included, 225 | and automatic rotator control via plugins is now supported. 226 | 227 | * The destination directory for PREDICT's executables may now be 228 | specified to something other than the regular default (/usr/local/bin) 229 | when ./configure is invoked. See the README file for details. 230 | 231 | * Main menu option [I] (Program Information) was changed to provide 232 | information about which files were loaded by PREDICT, if a soundcard 233 | is present, if network server mode is enabled, if rotator AutoTracking 234 | is enabled, and if so, what serial port is used, how often is data sent, 235 | etc. 236 | 237 | * The documentation was updated, and several other small coding 238 | changes were made. 239 | 240 | ---------------------------------------------------------------------------- 241 | 242 | Release 2.1.5: (a.k.a. The "Palindrome Year" Release) 243 | By John A. Magliacane (03-Jan-2002): 244 | 245 | * -n command-line switch was added by Xavier Crehueras, EB3CZS, 246 | to permit an alternate network port to be specified when 247 | running PREDICT in server mode. This flexibility permits 248 | multiple instances of PREDICT to run in server mode on a 249 | single host without interaction. Each instance of PREDICT 250 | can then operate from a different orbital database, and 251 | supply the tracking data generated to different clients. 252 | 253 | * -p command-line switch was added to permit a set of orbital 254 | predictions for a single pass to be generated by PREDICT and 255 | obtained solely through the command line. The output may be 256 | directed to a file if a name is specified through the -o switch. 257 | 258 | * The output of the -f command-line was modified to be consistent 259 | with that of the the newly added -p switch. Options to the -f 260 | switch were enhanced to permit a list of satellite positions to 261 | be generated in resolutions of one every second or one every 262 | minute. 263 | 264 | * The GET_SAT_POS socket command now provides date/time information 265 | in both ASCII and Unix Time, as well as range, orbit number, and 266 | sunlight visibility information in addition to azimuth, elevation, 267 | and sub-satellite point latitude and longitude (which are now 268 | ints instead of floats). 269 | 270 | * A new socket command called PREDICT was added to provide orbital 271 | prediction information through the socket interface. 272 | 273 | * Due to a warning given by gcc version 2.95.3, the use 274 | of tmpnam() was replaced by mkstemp() in predict.c. 275 | 276 | * Modified the "kepupdate" script to download Keplerian element data 277 | from celestrak.com using the wget command and the http protocol 278 | since celestrak.com no longer provides Keplerians via anonymous ftp. 279 | 280 | * The xpredict script was modified to include more virtual terminal 281 | selections. 282 | 283 | * Version 0.9.0 of "gsat" by Xavier Crehueras, EB3CZS, is included. 284 | Several new features, including automatic Doppler correction via 285 | plugins, is included. Details for writing and customizing plugins 286 | is provided. 287 | 288 | * Added a new "-x" command line option to "earthtrack" to allow passing 289 | extra parameters directly to xearth and xplanet (contributed by Tom 290 | Busch, WB8WOR). 291 | 292 | * The documentation was updated, and several other small coding 293 | changes were made. 294 | 295 | ---------------------------------------------------------------------------- 296 | 297 | Release 2.1.4: 298 | by John A. Magliacane (28-Jun-2001): 299 | 300 | * Fixed an array overflow problem in the Solar Illumination 301 | Prediction mode. 302 | 303 | * Pressing [R] in either the Single or Multi-Tracking mode forces 304 | PREDICT to re-read the orbital database. This can be useful if 305 | the database is updated manually or by means other than through 306 | PREDICT's update options while PREDICT is still running, and 307 | eliminates the need to re-start the program under such conditions. 308 | 309 | * Real-time tracking is no longer attempted for satellites that 310 | appear to have decayed from orbit. 311 | 312 | * Several new options were added to the -f command-line switch. If no 313 | starting or ending date/time is specified, a single line of output 314 | corresponding to the current date/time is produced. If the starting 315 | date/time is replaced by a number (n) preceded by a '+' symbol (ie. 316 | +10), output is produced starting at the current date/time, and 317 | ending at the current date/time plus 'n' seconds. 318 | 319 | * Multiple Keplerian element data files may now be used as arguments 320 | for the -u command-line switch. (predict -u file1 file2 file3) 321 | 322 | * Several network client applications were added or otherwise revised. 323 | "earthtrack" was modified to work not only with xearth, but with 324 | xplanet as well. When used with xplanet, earthtrack provides 325 | photo-realistic views of the earth from the perspective of the 326 | spacecraft being tracked by PREDICT. Very nice! An updated version 327 | of gsat (version 0.8.0) is also included, as well as a small utility 328 | to force a reload of PREDICT's orbital database through the UDP 329 | interface. 330 | 331 | * The configuration and compilation process has been modified slightly 332 | to reduce confusion during installation. Please check the README file 333 | for instructions! 334 | 335 | * An updated Linux Software Map entry was issued, and the new version 336 | of PREDICT was uploaded to: 337 | 338 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.1.4.tar.gz 339 | and 340 | ftp://ftp.metalab.unc.edu/pub/linux/apps/ham/predict-2.1.4.tar.gz 341 | 342 | ---------------------------------------------------------------------------- 343 | 344 | Release 2.1.3: (a.k.a. "The New Millennium Edition") 345 | by John A. Magliacane (06-Jan-2001): 346 | 347 | * Fixed several small math errors. One goes back to some very old 348 | (and widely used) satellite tracking code! Thanks to Jordi Mas 349 | for pointing them out and providing corrections. Thanks also to 350 | Andreas Trotschel for pointing out an error in the size of the 351 | global output[] array. 352 | 353 | * Portions of the MultiTrack() and SingleTrack() functions were 354 | re-coded for greater simplicity and to permit some new socket 355 | features to be added. 356 | 357 | * Added many new socket commands: 358 | 359 | RELOAD_TLE: forces a running version of PREDICT to 360 | re-read its orbital database on the fly. 361 | 362 | GET_SUN: returns the current azimuth and elevation 363 | headings of the Sun. 364 | 365 | GET_MOON: returns the current azimuth and elevation 366 | headings of the Moon. 367 | 368 | GET_QTH: returns the ground station's callsign, latitude, 369 | longitude, and altitude above sea level. 370 | 371 | GET_VERSION: returns PREDICT's version number. 372 | 373 | GET_TLE: returns Keplerian orbital data for a satellite 374 | based on its name or object number. 375 | 376 | GET_TIME: returns the current UTC date/time as reported by the 377 | server in the form of the number of seconds since 378 | 00:00:00 UTC on January 1, 1970 (Unix Time). 379 | 380 | GET_TIME$: returns the current UTC date/time as reported by 381 | the server in the form of Fri Dec 22 22:37:05 2000. 382 | 383 | GET_SAT_POS: returns the latitude, longitude, azimuth and elevation 384 | headings for a satellite at the time (or starting and 385 | ending times) specified. 386 | 387 | The GET_SAT command now returns the current slant-range to the satellite 388 | (in kilometers), the satellite's altitude, orbital velocity, current orbit 389 | number, and sunlight visibility information in addition to the tracking 390 | data previously provided. The "Next AOS" parameter previously returned 391 | has been changed to "Next Event Time", which corresponds to the Next AOS 392 | time if the satellite is not in range, or the LOS Time for satellites 393 | that are in range. Geostationary satellites, or satellites in orbits 394 | that do not permit passes above the ground station return a 0 for 395 | "Next Event Time". 396 | 397 | The GET_LIST command now returns ALL the satellite names in PREDICT's 398 | orbital database as a single string, and must be parsed by the client 399 | to pull out individual names. Earlier versions of PREDICT returned 400 | only one name at a time when the GET_LIST command was invoked. In 401 | addition, satellite names returned by GET_LIST are now full-length 402 | strings, and are no longer abbreviated by the program in any way. 403 | 404 | * The GET_DOPPLER command no longer echos back the name of the 405 | satellite for which the Doppler information was requested. 406 | 407 | * The socket documentation and "demo.c" program were significantly 408 | updated. 409 | 410 | * "demo.pl" written in Perl by Rich Parry, W9IF, is included to act 411 | as a template for those interested in writing client applications 412 | in Perl. 413 | 414 | * The main program documentation was re-organized around a common 415 | document written in groff, thereby providing a system man page 416 | as well as postscript, PDF, and ASCII documents having identical 417 | content. The HTML documentation was eliminated. 418 | 419 | * Added a new command-line option: -a When followed by a serial port 420 | device (eg: predict -a /dev/ttyS0), PREDICT sends real-time tracking 421 | data to the serial port specified in the form of: "AZ241.0 EL26.0\r\n" 422 | (EasyComm standard) when in the single satellite tracking mode. 423 | Updates are sent as the values for azimuth and elevation change over 424 | time. This feature is used to permit PREDICT to control an antenna 425 | rotator through an interface developed by Vicenzo Mezzalira, IW3FOL 426 | . Thanks to Vittorio Benvenuti, I3VFJ 427 | for contributing this code to PREDICT. 428 | 429 | * Added a new command-line option: -f When followed by a satellite 430 | name or object number, a date/time (in Unix Time), or a starting 431 | and ending date/time, the corresponding latitude, longitude, azimuth, 432 | and elevation headings of the satellite are returned by the program. 433 | This permits PREDICT to be invoked within other applications that 434 | need to determine the location of a satellite at a particular point 435 | in time, such as the location of where a CCD camera image was taken 436 | by a Pacsat satellite based on its timestamp. 437 | 438 | * Added a new line to the xpredict script to allow it to run under 439 | newer Linux distributions. Users need to select (uncomment) one 440 | of the lines in the script depending on their termcap settings. 441 | 442 | * Included the source code for the xforms-based graphical real-time 443 | satellite tracking display program "map" by Ivan Galysh, KD4HBO 444 | under clients/map. 445 | 446 | * Included the source code for the GTK-based graphical real-time 447 | satellite tracking display program "gsat" by Xavier Crehueras, 448 | EB3CZS under clients/gsat. 449 | 450 | * Included the source code for "earthtrack", a utility that permits the 451 | real-time display of satellite locations using the "xearth" program. 452 | It is located under clients/earthtrack. 453 | 454 | * An updated Linux Software Map entry was issued, and the new version 455 | of PREDICT was uploaded to: 456 | 457 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.1.3.tar.gz 458 | and 459 | ftp://ftp.metalab.unc.edu/pub/linux/apps/ham/predict-2.1.3.tar.gz 460 | 461 | ---------------------------------------------------------------------------- 462 | 463 | Release 2.1.2: 464 | by John A. Magliacane (17-Jun-2000): 465 | 466 | * Several small bugs in the KepCheck() and SaveTLE() functions of predict.c 467 | were identified and repaired. The problems centered on the fact that 468 | unsigned chars were used to accumulate TLE checksums. Occasionally, 469 | the checksums exceeded the capacity of an unsigned char, and rolled 470 | over through zero as a result. These variables were changed to 471 | unsigned integers. 472 | 473 | * A small problem in the AutoUpdate() function in predict.c was identified 474 | and fixed. Occasionally, Keplerian data for a satellite would not 475 | update if it had the same reference epoch as that in PREDICT's 476 | orbital database. This problem was the result of extremely small 477 | rounding errors introduced by the complier that caused intermittent 478 | logic errors when trying to compare two double precision floats for 479 | equality. This problem was solved by changing these variables from 480 | doubles to floats. 481 | 482 | * The array "filename" in the function sayfile() in vocalizer.c was 483 | increased in size from 40 to 80 bytes. Before the change, this array 484 | could overflow, causing a halt in the vocalizer routine on some machines 485 | before the proper end of the articulated message. 486 | 487 | * A #define was added to the geosat.c program to declare a value of PI 488 | for those compilers/platforms that require it being defined. 489 | 490 | * The -T option in the xpredict script was changed to -title. 491 | 492 | * An updated Linux Software Map entry was issued, and the new version 493 | of PREDICT was uploaded to: 494 | 495 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.1.2.tar.gz 496 | and 497 | ftp://ftp.metalab.unc.edu/pub/linux/apps/ham/predict-2.1.2.tar.gz 498 | 499 | ---------------------------------------------------------------------------- 500 | 501 | Release 2.1.1: 502 | by John A. Magliacane (03-Apr-2000): 503 | 504 | * A few bugs in the "installer" program were fixed. 505 | 506 | * The Keplerian "torture test" in predict.c was a bit too brutal for 507 | some element sets. The check for a '-' on position 59 of TLE line 1 508 | was eliminated since some element sets have a '+' in that position. 509 | 510 | * An updated Linux Software Map entry was issued, and the new version 511 | of PREDICT was uploaded to: 512 | 513 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.1.1.tar.gz 514 | and 515 | ftp://ftp.metalab.unc.edu/pub/linux/apps/ham/predict-2.1.1.tar.gz 516 | 517 | ---------------------------------------------------------------------------- 518 | 519 | Release 2.1.0: 520 | by John A. Magliacane (01-Apr-2000): 521 | 522 | * A solar illumination feature was added to predict the percentage 523 | of time a satellite spends in sunlight per day. 524 | 525 | * Orbital predictions for satellites that appear to have decayed 526 | since the last Keplerian orbital update are no longer attempted 527 | by the program. 528 | 529 | * Date and time used to start orbital predictions may now be 530 | abbreviated to Day/Month/Year only (00:00:00 is assumed). 531 | 532 | * System clock/calendar is now read to millisecond precision, 533 | permitting more "lively" real-time tracking mode displays. 534 | 535 | * "vocalizer" code was changed to use a slightly different (better) 536 | approach towards initializing and sending data to the system 537 | soundcard. 538 | 539 | * Socket-based server code was contributed by Ivan Galysh, KD4HBO. 540 | It allows PREDICT to export real-time tracking data for all 24 541 | satellites to external client programs, such as automatic antenna 542 | rotator controllers, graphical map display programs, or TX/RX 543 | tuning controllers. The server features are activated by invoking 544 | PREDICT with a -s switch. Communication between server and clients 545 | is by way of the UDP protocol. Real-time data is exported by PREDICT 546 | in both the single satellite and multi-satellite tracking modes. 547 | 548 | * The command-line parsing code was modified to prevent segmentation 549 | faults from occurring if command-line options are incorrectly issued. 550 | 551 | * Some minor coding changes were made. 552 | 553 | * An updated Linux Software Map entry was issued, and the new version 554 | of PREDICT was uploaded to: 555 | 556 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.1.0.tar.gz 557 | and 558 | ftp://ftp.metalab.unc.edu/pub/linux/apps/ham/predict-2.1.0.tar.gz 559 | 560 | ---------------------------------------------------------------------------- 561 | 562 | Release 2.0.1: 563 | by John A. Magliacane (02-Jan-2000): 564 | 565 | * Added this file. :-) 566 | 567 | * Replaced the printw() calls with addstr() calls in the Print() function. 568 | This may end the segmentation faults previously encountered when using 569 | ncurses versions earlier than 4.2. 570 | 571 | * Increased the capacity for supporting callsigns longer than 5 characters, 572 | even though I thought I had designed the program to handle 6! Through 573 | a series of "well-planned errors", callsigns longer than 5 characters 574 | caused all other QTH data entered through main menu option [G] to be 575 | set to zero. Also modified the ReadDataFiles() function to chop off 576 | the character appended to the callsign. 577 | 578 | * Drastically changed the installation program. Instead of having the 579 | installation program modify the source code for predict and vocalizer 580 | depending on the system hardware and installation directory chosen by 581 | the user, the installation program now writes all necessary information 582 | to a pair of .h files that are #included in the source code for both 583 | programs. (Why I didn't think of it earlier, I'll never know...) 584 | 585 | * Ported source code (with all the 2.0.1 modifications) to DOS using 586 | Caldera's DR-DOS 7.02 and a DJGPP 32-bit software developing environment. 587 | The vocalizer routines were intentionally left out (DOS can't multi-task). 588 | The initial DOS version has been set to 2.0.1d rather than 2.0.0 to reflect 589 | the small bug fixes it contains over the original Linux version, with 590 | the 'd' representing the fact that it's a DOS version. The DOS version 591 | contains full source code plus a pre-compiled binary for Intel CPUs 592 | as well as a protected mode driver. It was uploaded to: 593 | 594 | ftp://ftp.amsat.org/amsat/software/PC/tracking/predict2.zip 595 | 596 | * Uploaded the latest release to: 597 | 598 | ftp://ftp.amsat.org/amsat/software/Linux/predict-2.0.1.tar.gz 599 | 600 | -------------------------------------------------------------------------------- /licensing/COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | Appendix: How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) 19yy name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Library General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /licensing/CREDITS: -------------------------------------------------------------------------------- 1 | ************* 2 | * Credits * 3 | ************* 4 | 5 | SGP4/SDP4 models and sun/moon position algorithms used in this 6 | version of PREDICT were derived from "satcom-08", written by 7 | Neoklis Kyriazis, 5B4AZ, and released in 2002 under the GNU 8 | General Public License. Parts of "satcom-08" were derived 9 | through a translation into 'C' of SGP4 and SDP4 code written 10 | originally in FORTRAN and Pascal by Dr. T.S. Kelso. Dr. Kelso's 11 | code is available on his web site at http://www.celestrak.com/. 12 | 13 | Socket-based server code and the "map" client application were 14 | contributed by Ivan Galysh, KD4HBO on 10-Jan-2000. 15 | 16 | PIC/TRACK antenna rotator control code was contributed by 17 | Vittorio Benvenuti, I3VFJ on 13-Jul-2000. 18 | 19 | The "gsat" client was contributed by Xavier Crehueras, EB3CZS. 20 | 21 | The Perl client demonstration program was contributed by Rich 22 | Parry, W9IF. 23 | 24 | The WinPredictClient was contributed by Steve Fraser, VK5ASF. 25 | 26 | The concept of using "xearth" as a graphical display for PREDICT 27 | was originally conceived by Wade Hampton (that later developed 28 | into "earthtrack"). 29 | 30 | Many others have contributed code to the project, including 31 | Bent Bagger OZ6BL, Tom Busch WB8WOR, Steve Fraser VK5ASF, 32 | Xavier Crehueras, EB3CZS, Takeshi-Okazaki (Satellite Engineer 33 | for MTSAT-1R (HIMAWARI 6)), and others. 34 | 35 | Calculations for squint angle were adopted from trk-0.4.1 by Lapo 36 | Pieri, IK5NAX. 37 | 38 | PREDICT is also the product of many others who have contributed 39 | bug reports, bug fixes, helpful suggestions, and positive support. 40 | 41 | 42 | -- 43 | John A. Magliacane, KD2BD 44 | Lead Developer 45 | kd2bd@amsat.org 46 | November 2005 47 | 48 | -------------------------------------------------------------------------------- /licensing/HISTORY: -------------------------------------------------------------------------------- 1 | ====================== 2 | The History of PREDICT 3 | ====================== 4 | 5 | Work on PREDICT started on May 26, 1991 with the purpose of creating a 6 | replacement for QUIKTRAK software that ran on the Commodore 64 home 7 | computer. PREDICT was originally written in C and compiled using a 8 | "Super C" compiler. After the program was completed and found to be 9 | useful, PREDICT was then ported to several Unix machines and then to 10 | DOS using a Borland Turbo C++ compiler. The DOS version was completed 11 | in May 1994 and was the first version of PREDICT released to the public. 12 | PREDICT was later ported to the Linux operating system in 1995 and 13 | released as freeware as a pre-compiled a.out dynamically linked 14 | binary in 1996. 15 | 16 | Through years of daily use, PREDICT was found to be reliable and accurate, 17 | and only minor changes were made to the program until 1999 when major 18 | portions were re-written from scratch and many new features were added. 19 | 20 | The release of PREDICT version 2.0.0 in December 1999 represents the 21 | second major release of the program. 22 | 23 | Further details regarding the history of PREDICT are available in the 24 | July 2000 (Science and Engineering) issue of "Linux Journal" magazine. 25 | 26 | The release of PREDICT version 2.2.0 in October 2002 represents the 27 | third major release of the program. 28 | 29 | This version of PREDICT was written under Slackware Linux on a 100 MHz 30 | Pentium-based PC with 16 megabytes of RAM using the "vi" editor. 31 | 32 | -------------------------------------------------------------------------------- /licensing/NEWS: -------------------------------------------------------------------------------- 1 | PREDICT 2.2.0 Release 2 | ===================== 3 | The major revisions in PREDICT 2.2.0 include the use of 4 | SGP4/SDP4 orbital models for improved tracking accuracy, 5 | as well as a re-written Single Satellite Tracking Mode 6 | to provide more useful real-time satellite communications 7 | data. 8 | 9 | 10 | New Orbital Models 11 | ================== 12 | The simplified orbital model used in previous versions 13 | of PREDICT provided very accurate and dependable results 14 | for the majority of users, but was not quite good enough 15 | for more demanding applications at NASA. 16 | 17 | In July 2002, a test was performed on the accuracy of the 18 | orbital model used in PREDICT. The test was performed by 19 | a software engineer working at Edwards Airforce Base in 20 | California, and involved comparing data provided by PREDICT's 21 | Calc() and PreCalc() functions to that of a radar system 22 | used to track the International Space Station. Ranging 23 | data from the radar was taken every 50 milliseconds for 24 | the duration of the pass. 25 | 26 | The error in azimuth was found to be almost zero at AOS, and 27 | increased to 0.3 degrees over the duration of the pass. The 28 | elevation was about 0.2 degrees lower than that measured by 29 | radar for the entire pass. The calculated range was approximately 30 | 7000 meters low at AOS, and approximately 7000 meters high at 31 | LOS with a crossover at TCA. 32 | 33 | 34 | New Single Satellite Tracking Mode 35 | ================================== 36 | The Single Satellite Tracking Mode has been completely revised 37 | in PREDICT version 2.2.0. PREDICT now supports a database of 38 | satellite transponder data so that the program can provide 39 | path loss and Doppler-corrected uplink and downlink frequency 40 | information in real-time. For satellites containing linear 41 | transponders, uplink and downlink frequencies may be adjusted 42 | by pressing the '<' and '>' keys to tune down across the 43 | transponder in 1 kHz steps, or the ',' and '.' keys (unshifted 44 | '<' and '>') to tune in 100 Hz steps. Switching between 45 | transponders may be accomplished by pressing the SPACE bar. 46 | 47 | Eclipse depth, an angle representing how deep a satellite is 48 | in solar eclipse, is also provided. Eclipse depth is positive 49 | when the spacecraft is in eclipse, and negative when it is in 50 | sunlight. Spacecraft antenna squint angle is provided if 51 | ALAT/ALON (spacecraft attitude coordinates) parameters are 52 | included in the transponder database file. 53 | 54 | 55 | Transponder Database 56 | ==================== 57 | When PREDICT version 2.2.0 is initially run, a default transponder 58 | database file (predict.db) is copied into the user's $HOME/.predict 59 | directory. At present, the database provides for many new features, 60 | som of which have not been completely implemented by PREDICT in 61 | this early release of the 2.2.x run. The most glaring omission 62 | is lack of a facility to edit or modify the database file. For 63 | the time being, it may be edited by hand if care is excercised. 64 | 65 | The format for the database file is as follows. Using NOAA-17 66 | as an example: 67 | 68 | NOAA-17 Name of satellite 69 | 27453 Object number (must match predict.tle) 70 | No alat/alon ALAT/ALON spacecraft attitude information 71 | APT Downlink Description of transmitter/transponder 72 | 0.0, 0.0 Uplink passband limits in MHz (0.0 = no uplink) 73 | 137.620, 137.620 Corresponding downlink passband limits (single downlink) 74 | No weekly schedule Days of the week this transponder is active 75 | No orbital schedule Orbital phase when this transponder is active 76 | HRPT Downlink Description of second transmitter/transponder 77 | 0.0, 0.0 Uplink passband limits (low end limit, high end) 78 | 1707.0, 1707.0 Corresponding downlink passband limits (single downlink) 79 | No weekly schedule Days of the week this transponder is active 80 | No orbital schedule Orbital phase when this transponder is active 81 | end End of data for this satellite 82 | 83 | OSCAR-29 contains linear (inverting) as well as digital (Pacsat) 84 | transponders: 85 | 86 | OSCAR-29 87 | 24278 88 | No alat/alon 89 | Mode JA Transponder 90 | 145.900, 146.000 91 | 435.900, 435.800 92 | No weekly schedule 93 | No orbital schedule 94 | Mode JD Channel A 95 | 145.850, 145.850 96 | 435.910, 435.910 97 | No weekly schedule 98 | No orbital schedule 99 | Mode JD Channel B 100 | 145.870, 145.870 101 | 435.910, 435.910 102 | No weekly schedule 103 | No orbital schedule 104 | Mode JD Channel C 105 | 145.910, 145.910 106 | 435.910, 435.910 107 | No weekly schedule 108 | No orbital schedule 109 | end 110 | 111 | Weekly and orbital schedules have not yet been implemented in 112 | PREDICT 2.2.0, so for the time being, entries for these parameters 113 | should begin with the word "No". ALAT/ALON should be entered as 114 | 30.0, 0.0 for alat=30.0 and alon=0.0. See $HOME/.predict/predict.db 115 | for further examples. 116 | 117 | Entries in predict.db need not be in the same order as they appear 118 | in predict.tle. Satellite names need not match either, but the 119 | object numbers in predict.db must be correct. predict.db may contain 120 | more satellite entries than those appearing in predict.tle (normally 24). 121 | PREDICT reads the entire database file, and reads only the information 122 | that applies to the satellites in the predict.tle file in use at the 123 | time. If no transponder data is available, or if a satellite contains 124 | no communication transponders, the Single Satellite tracking mode 125 | screen is modified accordingly. 126 | 127 | Future revisions of PREDICT will likely include a database editor 128 | as well as possible modifications to the format of the database. 129 | 130 | 131 | Other changes 132 | ============= 133 | PREDICT now installs into a directory whose name includes the revision 134 | number (ie: predict-2.2.0). 135 | 136 | Despite the major revisions to the tracking code, all other program 137 | functions, with the exception of the Single Satellite Tracking Mode, 138 | should act as they did in earlier versions. 139 | 140 | 141 | -- 142 | John, KD2BD 143 | October 12, 2002 144 | 145 | -------------------------------------------------------------------------------- /licensing/README: -------------------------------------------------------------------------------- 1 | ========================= 2 | PREDICT QUICK START GUIDE 3 | ========================= 4 | 5 | SYSTEM REQUIREMENTS 6 | =================== 7 | This program requires ncurses version 4.2 or higher. Earlier versions have 8 | been known to cause segmentation faults and/or odd display colors with this 9 | program. ncurses may be obtained via anonymous FTP at: 10 | 11 | prep.ai.mit.edu 12 | 13 | in the pub/gnu/ncurses subdirectory 14 | 15 | or: 16 | http://www.gnu.org/software/ncurses/ncurses.html 17 | 18 | 19 | The Linux pthreads library is also required. Both of these libraries are 20 | usually available in modern Linux distributions. 21 | 22 | 23 | UNPACKING 24 | ========= 25 | The PREDICT archive can be unpacked in any directory for which read access 26 | is provided for all system users (such as /usr/src or /usr/local/src for 27 | Slackware Linux). As root, move the tar file to such a directory and 28 | issue the command: 29 | 30 | tar xvfz predict-2.2.3.tar.gz 31 | 32 | to unpack the file. 33 | 34 | 35 | COMPILATION 36 | =========== 37 | PREDICT's compilation and installation procedure differs slightly from 38 | that of most software, but should easy enough to understand and follow. 39 | First, move (cd) into the predict directory and execute the included 40 | "configure" script by typing: 41 | 42 | ./configure 43 | 44 | as root at your command prompt. This script compiles and runs the install 45 | program that configures the source code for PREDICT. It then compiles and 46 | installs PREDICT by creating symbolic links between the executables created 47 | in the installation directory and /usr/local/bin. If a destination directory 48 | other than /usr/local/bin is desired, invoke configure with the desired 49 | directory as an argument on the command line: 50 | 51 | ./configure /usr/bin 52 | 53 | NOTE: "configure" may stall if the system soundcard is in use. Some 54 | window managers (sometimes KDE) take control of the soundcard, and prevent 55 | the configure script from opening /dev/dsp and checking its existence. 56 | 57 | 58 | FIRST TIME USE 59 | ============== 60 | First time users will be asked to enter their groundstation latitude and 61 | longitude in decimal degrees or in degree, minute, second (DMS) format. 62 | Normally, PREDICT handles longitudes in decimal degrees WEST (0-360 63 | degrees), and latitudes in decimal degrees NORTH. This behavior can be 64 | modified by passing the -east or -south command line switches to PREDICT 65 | when it is invoked. Your station's altitude in meters above mean sea 66 | level, a recent set of Keplerian orbital data for the satellites of 67 | interest, and an accurately set system clock are also required if 68 | successful real-time satellite tracking is also desired. Sources 69 | for such data include http://www.celestrak.com/, 70 | http://www.space-track.org, and http://www.amsat.org/. 71 | 72 | 73 | FOR FURTHER INFORMATION 74 | ======================= 75 | Please consult the files under the "docs" subdirectory for more 76 | directions on the use and capabilities of PREDICT. 77 | 78 | The latest news and information regarding PREDICT software is 79 | available at: http://www.qsl.net/kd2bd/predict.html. 80 | 81 | 82 | Happy Tracking! 83 | 84 | -- 85 | John, KD2BD 86 | kd2bd@amsat.org 87 | May 2006 88 | 89 | -------------------------------------------------------------------------------- /predict.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************\ 2 | * PREDICT: A satellite tracking/orbital prediction program * 3 | * Project started 26-May-1991 by John A. Magliacane, KD2BD * 4 | * Last update: 14-May-2006 * 5 | ***************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify it * 8 | * under the terms of the GNU General Public License as published by the * 9 | * Free Software Foundation; either version 2 of the License or any later * 10 | * version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 15 | * General Public License for more details. * 16 | * * 17 | ***************************************************************************** 18 | * See the "CREDITS" file for the names of those who have * 19 | * generously contributed their time, talent, and effort to this project. * 20 | \***************************************************************************/ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define VERSION "2.2.3" 35 | 36 | /* Constants used by SGP4/SDP4 code */ 37 | 38 | #define km2mi 0.621371 /* km to miles */ 39 | #define deg2rad 1.745329251994330E-2 /* Degrees to radians */ 40 | #define pi 3.14159265358979323846 /* Pi */ 41 | #define pio2 1.57079632679489656 /* Pi/2 */ 42 | #define x3pio2 4.71238898038468967 /* 3*Pi/2 */ 43 | #define twopi 6.28318530717958623 /* 2*Pi */ 44 | #define e6a 1.0E-6 45 | #define tothrd 6.6666666666666666E-1 /* 2/3 */ 46 | #define xj2 1.0826158E-3 /* J2 Harmonic (WGS '72) */ 47 | #define xj3 -2.53881E-6 /* J3 Harmonic (WGS '72) */ 48 | #define xj4 -1.65597E-6 /* J4 Harmonic (WGS '72) */ 49 | #define xke 7.43669161E-2 50 | #define xkmper 6.378137E3 /* WGS 84 Earth radius km */ 51 | #define xmnpda 1.44E3 /* Minutes per day */ 52 | #define ae 1.0 53 | #define ck2 5.413079E-4 54 | #define ck4 6.209887E-7 55 | #define f 3.35281066474748E-3 /* Flattening factor */ 56 | #define ge 3.986008E5 /* Earth gravitational constant (WGS '72) */ 57 | #define s 1.012229 58 | #define qoms2t 1.880279E-09 59 | #define secday 8.6400E4 /* Seconds per day */ 60 | #define omega_E 1.00273790934 /* Earth rotations/siderial day */ 61 | #define omega_ER 6.3003879 /* Earth rotations, rads/siderial day */ 62 | #define zns 1.19459E-5 63 | #define c1ss 2.9864797E-6 64 | #define zes 1.675E-2 65 | #define znl 1.5835218E-4 66 | #define c1l 4.7968065E-7 67 | #define zel 5.490E-2 68 | #define zcosis 9.1744867E-1 69 | #define zsinis 3.9785416E-1 70 | #define zsings -9.8088458E-1 71 | #define zcosgs 1.945905E-1 72 | #define zcoshs 1 73 | #define zsinhs 0 74 | #define q22 1.7891679E-6 75 | #define q31 2.1460748E-6 76 | #define q33 2.2123015E-7 77 | #define g22 5.7686396 78 | #define g32 9.5240898E-1 79 | #define g44 1.8014998 80 | #define g52 1.0508330 81 | #define g54 4.4108898 82 | #define root22 1.7891679E-6 83 | #define root32 3.7393792E-7 84 | #define root44 7.3636953E-9 85 | #define root52 1.1428639E-7 86 | #define root54 2.1765803E-9 87 | #define thdt 4.3752691E-3 88 | #define rho 1.5696615E-1 89 | #define mfactor 7.292115E-5 90 | #define sr 6.96000E5 /* Solar radius - km (IAU 76) */ 91 | #define AU 1.49597870691E8 /* Astronomical unit - km (IAU 76) */ 92 | 93 | /* Entry points of Deep() */ 94 | 95 | #define dpinit 1 /* Deep-space initialization code */ 96 | #define dpsec 2 /* Deep-space secular code */ 97 | #define dpper 3 /* Deep-space periodic code */ 98 | 99 | /* Flow control flag definitions */ 100 | 101 | #define ALL_FLAGS -1 102 | #define SGP_INITIALIZED_FLAG 0x000001 /* not used */ 103 | #define SGP4_INITIALIZED_FLAG 0x000002 104 | #define SDP4_INITIALIZED_FLAG 0x000004 105 | #define SGP8_INITIALIZED_FLAG 0x000008 /* not used */ 106 | #define SDP8_INITIALIZED_FLAG 0x000010 /* not used */ 107 | #define SIMPLE_FLAG 0x000020 108 | #define DEEP_SPACE_EPHEM_FLAG 0x000040 109 | #define LUNAR_TERMS_DONE_FLAG 0x000080 110 | #define NEW_EPHEMERIS_FLAG 0x000100 /* not used */ 111 | #define DO_LOOP_FLAG 0x000200 112 | #define RESONANCE_FLAG 0x000400 113 | #define SYNCHRONOUS_FLAG 0x000800 114 | #define EPOCH_RESTART_FLAG 0x001000 115 | #define VISIBLE_FLAG 0x002000 116 | #define SAT_ECLIPSED_FLAG 0x004000 117 | 118 | // Python Extension Globals 119 | static PyObject *PredictException; 120 | static PyObject *NoTransitException; 121 | 122 | // This struct represents an observation of a particular satellite 123 | // from a particular reference point (on earth) at a particular time 124 | // and is used primarily in the PyPredict code. 125 | typedef struct observation { 126 | double epoch; 127 | char orbital_model[5]; 128 | long norad_id; 129 | char name[25]; 130 | double latitude; 131 | double longitude; 132 | double altitude; 133 | double orbital_velocity; 134 | double footprint; 135 | double eclipse_depth; 136 | double orbital_phase; 137 | char sunlit; 138 | long orbit; 139 | char geostationary; 140 | double azimuth; 141 | double elevation; 142 | double slant_range; 143 | char visibility; 144 | char has_aos; 145 | char decayed; 146 | double doppler; 147 | double eci_x; 148 | double eci_y; 149 | double eci_z; 150 | double eci_vx; 151 | double eci_vy; 152 | double eci_vz; 153 | double eci_sun_x; 154 | double eci_sun_y; 155 | double eci_sun_z; 156 | double eci_obs_x; 157 | double eci_obs_y; 158 | double eci_obs_z; 159 | double beta_angle; 160 | 161 | } observation; 162 | 163 | struct { 164 | char line1[70]; // First line of TLE 165 | char line2[70]; // Second line of TLE 166 | char name[25]; // Spacecraft Name 167 | long catnum; // Catalog Number (a.k.a NORAD id) 168 | long setnum; // Element Set No. 169 | char designator[10]; // Designator 170 | int year; // Reference Epoch from TLE (2-digit year) 171 | double refepoch; // Reference Epoch from TLE (day of the year and fractional portion of the day) 172 | double incl; // Inclination 173 | double raan; // RAAN 174 | double eccn; // Eccentricity 175 | double argper; // Arg of Perigee 176 | double meanan; // Mean Anomaly 177 | double meanmo; // Mean Motion 178 | double drag; // Decay Rate 179 | double nddot6; // Nddot/6 180 | double bstar; // Bstar Drag Term 181 | long orbitnum; // Orbit Number 182 | } sat; 183 | 184 | struct { 185 | char callsign[17]; // Observation Position Call Sign 186 | double stnlat; // Observation Position Latitude 187 | double stnlong; // Observation Position Longitude 188 | int stnalt; // Observation Position Altitude 189 | } qth; 190 | 191 | // TODO: sat_db doesn't seem to be used much. Verify it functions and annotate. 192 | struct { char name[25]; 193 | long catnum; 194 | char squintflag; 195 | double alat; 196 | double alon; 197 | unsigned char transponders; 198 | char transponder_name[10][80]; 199 | double uplink_start[10]; 200 | double uplink_end[10]; 201 | double downlink_start[10]; 202 | double downlink_end[10]; 203 | unsigned char dayofweek[10]; 204 | int phase_start[10]; 205 | int phase_end[10]; 206 | } sat_db; 207 | 208 | /* Global variables for sharing data among functions... */ 209 | 210 | double tsince, jul_epoch, jul_utc, eclipse_depth=0, 211 | sat_azi, sat_ele, sat_range, sat_range_rate, 212 | sat_lat, sat_lon, sat_alt, sat_vel, phase, 213 | sun_azi, sun_ele, daynum, fm, fk, age, aostime, 214 | lostime, ax, ay, az, rx, ry, rz, squint, alat, alon, 215 | eci_x, eci_y, eci_z, eci_vx, eci_vy, eci_vz, beta_angle, 216 | eci_sun_x, eci_sun_y, eci_sun_z, eci_obs_x, eci_obs_y, eci_obs_z, 217 | sun_ra, sun_dec, sun_lat, sun_lon, sun_range, sun_range_rate, 218 | moon_az, moon_el, moon_dx, moon_ra, moon_dec, moon_gha, moon_dv; 219 | 220 | char qthfile[50], tlefile[50], dbfile[50], temp[80], output[25], 221 | ephem[5], sat_sun_status, findsun, calc_squint; 222 | 223 | int indx, iaz, iel, ma256, isplat, isplong, Flags=0; 224 | 225 | long rv, irk; 226 | 227 | unsigned char val[256]; 228 | 229 | /** Type definitions **/ 230 | 231 | /* Two-line-element satellite orbital data 232 | structure used directly by the SGP4/SDP4 code. */ 233 | 234 | typedef struct { 235 | double epoch, xndt2o, xndd6o, bstar, xincl, 236 | xnodeo, eo, omegao, xmo, xno; 237 | int catnr, elset, revnum; 238 | char sat_name[25], idesg[9]; 239 | } tle_t; 240 | 241 | /* Geodetic position structure used by SGP4/SDP4 code. */ 242 | 243 | typedef struct { 244 | double lat, lon, alt, theta; 245 | } geodetic_t; 246 | 247 | /* General three-dimensional vector structure used by SGP4/SDP4 code. */ 248 | 249 | typedef struct { 250 | double x, y, z, w; 251 | } vector_t; 252 | 253 | /* Common arguments between deep-space functions used by SGP4/SDP4 code. */ 254 | 255 | typedef struct { 256 | /* Used by dpinit part of Deep() */ 257 | double eosq, sinio, cosio, betao, aodp, theta2, 258 | sing, cosg, betao2, xmdot, omgdot, xnodot, xnodp; 259 | 260 | /* Used by dpsec and dpper parts of Deep() */ 261 | double xll, omgadf, xnode, em, xinc, xn, t; 262 | 263 | /* Used by thetg and Deep() */ 264 | double ds50; 265 | } deep_arg_t; 266 | 267 | /* Global structure used by SGP4/SDP4 code. */ 268 | 269 | geodetic_t obs_geodetic; 270 | 271 | /* Two-line Orbital Elements for the satellite used by SGP4/SDP4 code. */ 272 | 273 | tle_t tle; 274 | 275 | /* Functions for testing and setting/clearing flags used in SGP4/SDP4 code */ 276 | 277 | int isFlagSet(int flag) 278 | { 279 | return (Flags&flag); 280 | } 281 | 282 | int isFlagClear(int flag) 283 | { 284 | return (~Flags&flag); 285 | } 286 | 287 | void SetFlag(int flag) 288 | { 289 | Flags|=flag; 290 | } 291 | 292 | void ClearFlag(int flag) 293 | { 294 | Flags&=~flag; 295 | } 296 | 297 | /* Remaining SGP4/SDP4 code follows... */ 298 | 299 | int Sign(double arg) 300 | { 301 | /* Returns sign of a double */ 302 | 303 | if (arg>0) 304 | { 305 | return 1; 306 | } 307 | 308 | else if (arg<0) 309 | { 310 | return -1; 311 | } 312 | 313 | else 314 | { 315 | return 0; 316 | } 317 | } 318 | 319 | double Sqr(double arg) 320 | { 321 | /* Returns square of a double */ 322 | return (arg*arg); 323 | } 324 | 325 | double Cube(double arg) 326 | { 327 | /* Returns cube of a double */ 328 | return (arg*arg*arg); 329 | } 330 | 331 | double Radians(double arg) 332 | { 333 | /* Returns angle in radians from argument in degrees */ 334 | return (arg*deg2rad); 335 | } 336 | 337 | double Degrees(double arg) 338 | { 339 | /* Returns angle in degrees from argument in radians */ 340 | return (arg/deg2rad); 341 | } 342 | 343 | double ArcSin(double arg) 344 | { 345 | /* Returns the arcsine of the argument */ 346 | 347 | if (fabs(arg)>=1.0) 348 | { 349 | return(Sign(arg)*pio2); 350 | } 351 | else 352 | { 353 | return(atan(arg/sqrt(1.0-arg*arg))); 354 | } 355 | } 356 | 357 | double ArcCos(double arg) 358 | { 359 | /* Returns arccosine of argument */ 360 | return(pio2-ArcSin(arg)); 361 | } 362 | 363 | void Magnitude(vector_t *v) 364 | { 365 | /* Calculates scalar magnitude of a vector_t argument */ 366 | v->w=sqrt(Sqr(v->x)+Sqr(v->y)+Sqr(v->z)); 367 | } 368 | 369 | void Vec_Add(vector_t *v1, vector_t *v2, vector_t *v3) 370 | { 371 | /* Adds vectors v1 and v2 together to produce v3 */ 372 | v3->x=v1->x+v2->x; 373 | v3->y=v1->y+v2->y; 374 | v3->z=v1->z+v2->z; 375 | Magnitude(v3); 376 | } 377 | 378 | void Vec_Sub(vector_t *v1, vector_t *v2, vector_t *v3) 379 | { 380 | /* Subtracts vector v2 from v1 to produce v3 */ 381 | v3->x=v1->x-v2->x; 382 | v3->y=v1->y-v2->y; 383 | v3->z=v1->z-v2->z; 384 | Magnitude(v3); 385 | } 386 | 387 | void Scalar_Multiply(double k, vector_t *v1, vector_t *v2) 388 | { 389 | /* Multiplies the vector v1 by the scalar k to produce the vector v2 */ 390 | v2->x=k*v1->x; 391 | v2->y=k*v1->y; 392 | v2->z=k*v1->z; 393 | v2->w=fabs(k)*v1->w; 394 | } 395 | 396 | void Scale_Vector(double k, vector_t *v) 397 | { 398 | /* Multiplies the vector v1 by the scalar k */ 399 | v->x*=k; 400 | v->y*=k; 401 | v->z*=k; 402 | Magnitude(v); 403 | } 404 | 405 | double Dot(vector_t *v1, vector_t *v2) 406 | { 407 | /* Returns the dot product of two vectors */ 408 | return (v1->x*v2->x+v1->y*v2->y+v1->z*v2->z); 409 | } 410 | 411 | double Angle(vector_t *v1, vector_t *v2) 412 | { 413 | /* Calculates the angle between vectors v1 and v2 */ 414 | Magnitude(v1); 415 | Magnitude(v2); 416 | return(ArcCos(Dot(v1,v2)/(v1->w*v2->w))); 417 | } 418 | 419 | void Cross(vector_t *v1, vector_t *v2 ,vector_t *v3) 420 | { 421 | /* Produces cross product of v1 and v2, and returns in v3 */ 422 | v3->x=v1->y*v2->z-v1->z*v2->y; 423 | v3->y=v1->z*v2->x-v1->x*v2->z; 424 | v3->z=v1->x*v2->y-v1->y*v2->x; 425 | Magnitude(v3); 426 | } 427 | 428 | void Normalize(vector_t *v) 429 | { 430 | /* Normalizes a vector */ 431 | v->x/=v->w; 432 | v->y/=v->w; 433 | v->z/=v->w; 434 | } 435 | 436 | double AcTan(double sinx, double cosx) 437 | { 438 | /* Four-quadrant arctan function */ 439 | 440 | if (cosx==0.0) 441 | { 442 | if (sinx>0.0) 443 | { 444 | return (pio2); 445 | } 446 | else 447 | { 448 | return (x3pio2); 449 | } 450 | } 451 | 452 | else 453 | { 454 | if (cosx>0.0) 455 | { 456 | if (sinx>0.0) 457 | { 458 | return (atan(sinx/cosx)); 459 | } 460 | else 461 | { 462 | return (twopi+atan(sinx/cosx)); 463 | } 464 | } 465 | 466 | else 467 | { 468 | return (pi+atan(sinx/cosx)); 469 | } 470 | } 471 | } 472 | 473 | double FMod2p(double x) 474 | { 475 | /* Returns mod 2PI of argument */ 476 | 477 | int i; 478 | double ret_val; 479 | 480 | ret_val=x; 481 | i=ret_val/twopi; 482 | ret_val-=i*twopi; 483 | 484 | if (ret_val<0.0) 485 | { 486 | ret_val+=twopi; 487 | } 488 | 489 | return ret_val; 490 | } 491 | 492 | double Modulus(double arg1, double arg2) 493 | { 494 | /* Returns arg1 mod arg2 */ 495 | 496 | int i; 497 | double ret_val; 498 | 499 | ret_val=arg1; 500 | i=ret_val/arg2; 501 | ret_val-=i*arg2; 502 | 503 | if (ret_val<0.0) 504 | { 505 | ret_val+=arg2; 506 | } 507 | 508 | return ret_val; 509 | } 510 | 511 | double Frac(double arg) 512 | { 513 | /* Returns fractional part of double argument */ 514 | return(arg-floor(arg)); 515 | } 516 | 517 | int Round(double arg) 518 | { 519 | /* Returns argument rounded up to nearest integer */ 520 | return((int)floor(arg+0.5)); 521 | } 522 | 523 | double Int(double arg) 524 | { 525 | /* Returns the floor integer of a double arguement, as double */ 526 | return(floor(arg)); 527 | } 528 | 529 | void Convert_Sat_State(vector_t *pos, vector_t *vel) 530 | { 531 | /* Converts the satellite's position and velocity */ 532 | /* vectors from normalized values to km and km/sec */ 533 | Scale_Vector(xkmper, pos); 534 | Scale_Vector(xkmper*xmnpda/secday, vel); 535 | } 536 | 537 | double Julian_Date_of_Year(double year) 538 | { 539 | /* The function Julian_Date_of_Year calculates the Julian Date */ 540 | /* of Day 0.0 of {year}. This function is used to calculate the */ 541 | /* Julian Date of any date by using Julian_Date_of_Year, DOY, */ 542 | /* and Fraction_of_Day. */ 543 | 544 | /* Astronomical Formulae for Calculators, Jean Meeus, */ 545 | /* pages 23-25. Calculate Julian Date of 0.0 Jan year */ 546 | 547 | long A, B, i; 548 | double jdoy; 549 | 550 | year=year-1; 551 | i=year/100; 552 | A=i; 553 | i=A/4; 554 | B=2-A+i; 555 | i=365.25*year; 556 | i+=30.6001*14; 557 | jdoy=i+1720994.5+B; 558 | 559 | return jdoy; 560 | } 561 | 562 | double Julian_Date_of_Epoch(double epoch) 563 | { 564 | /* The function Julian_Date_of_Epoch returns the Julian Date of */ 565 | /* an epoch specified in the format used in the NORAD two-line */ 566 | /* element sets. It has been modified to support dates beyond */ 567 | /* the year 1999 assuming that two-digit years in the range 00-56 */ 568 | /* correspond to 2000-2056. Until the two-line element set format */ 569 | /* is changed, it is only valid for dates through 2056 December 31. */ 570 | 571 | double year, day; 572 | 573 | /* Modification to support Y2K */ 574 | /* Valid 1957 through 2056 */ 575 | 576 | day=modf(epoch*1E-3, &year)*1E3; 577 | 578 | if (year<57) 579 | { 580 | year=year+2000; 581 | } 582 | else 583 | { 584 | year=year+1900; 585 | } 586 | 587 | return (Julian_Date_of_Year(year)+day); 588 | } 589 | 590 | int DOY (int yr, int mo, int dy) 591 | { 592 | /* The function DOY calculates the day of the year for the specified */ 593 | /* date. The calculation uses the rules for the Gregorian calendar */ 594 | /* and is valid from the inception of that calendar system. */ 595 | 596 | const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 597 | int i, day; 598 | 599 | day=0; 600 | 601 | for (i=0; i2)) 611 | { 612 | day++; 613 | } 614 | 615 | return day; 616 | } 617 | 618 | double Fraction_of_Day(int hr, int mi, double se) 619 | { 620 | /* Fraction_of_Day calculates the fraction of */ 621 | /* a day passed at the specified input time. */ 622 | 623 | double dhr, dmi; 624 | 625 | dhr=(double)hr; 626 | dmi=(double)mi; 627 | 628 | return ((dhr+(dmi+se/60.0)/60.0)/24.0); 629 | } 630 | 631 | double Julian_Date(struct tm *cdate) 632 | { 633 | /* The function Julian_Date converts a standard calendar */ 634 | /* date and time to a Julian Date. The procedure Date_Time */ 635 | /* performs the inverse of this function. */ 636 | 637 | double julian_date; 638 | 639 | julian_date=Julian_Date_of_Year(cdate->tm_year)+DOY(cdate->tm_year,cdate->tm_mon,cdate->tm_mday)+Fraction_of_Day(cdate->tm_hour,cdate->tm_min,cdate->tm_sec)+5.787037e-06; /* Round up to nearest 1 sec */ 640 | 641 | return julian_date; 642 | } 643 | 644 | void Date_Time(double julian_date, struct tm *cdate) 645 | { 646 | /* The function Date_Time() converts a Julian Date to 647 | standard calendar date and time. The function 648 | Julian_Date() performs the inverse of this function. */ 649 | 650 | time_t jtime; 651 | 652 | jtime=(julian_date-2440587.5)*86400.0; 653 | *cdate=*gmtime(&jtime); 654 | } 655 | 656 | double Delta_ET(double year) 657 | { 658 | /* The function Delta_ET has been added to allow calculations on */ 659 | /* the position of the sun. It provides the difference between UT */ 660 | /* (approximately the same as UTC) and ET (now referred to as TDT).*/ 661 | /* This function is based on a least squares fit of data from 1950 */ 662 | /* to 1991 and will need to be updated periodically. */ 663 | 664 | /* Values determined using data from 1950-1991 in the 1990 665 | Astronomical Almanac. See DELTA_ET.WQ1 for details. */ 666 | 667 | double delta_et; 668 | 669 | delta_et=26.465+0.747622*(year-1950)+1.886913*sin(twopi*(year-1975)/33); 670 | 671 | return delta_et; 672 | } 673 | 674 | double ThetaG(double epoch, deep_arg_t *deep_arg) 675 | { 676 | /* The function ThetaG calculates the Greenwich Mean Sidereal Time */ 677 | /* for an epoch specified in the format used in the NORAD two-line */ 678 | /* element sets. It has now been adapted for dates beyond the year */ 679 | /* 1999, as described above. The function ThetaG_JD provides the */ 680 | /* same calculation except that it is based on an input in the */ 681 | /* form of a Julian Date. */ 682 | 683 | /* Reference: The 1992 Astronomical Almanac, page B6. */ 684 | 685 | double year, day, UT, jd, TU, GMST, ThetaG; 686 | 687 | /* Modification to support Y2K */ 688 | /* Valid 1957 through 2056 */ 689 | 690 | day=modf(epoch*1E-3,&year)*1E3; 691 | 692 | if (year<57) 693 | { 694 | year+=2000; 695 | } 696 | else 697 | { 698 | year+=1900; 699 | } 700 | 701 | UT=modf(day,&day); 702 | jd=Julian_Date_of_Year(year)+day; 703 | TU=(jd-2451545.0)/36525; 704 | GMST=24110.54841+TU*(8640184.812866+TU*(0.093104-TU*6.2E-6)); 705 | GMST=Modulus(GMST+secday*omega_E*UT,secday); 706 | ThetaG=twopi*GMST/secday; 707 | deep_arg->ds50=jd-2433281.5+UT; 708 | ThetaG=FMod2p(6.3003880987*deep_arg->ds50+1.72944494); 709 | 710 | return ThetaG; 711 | } 712 | 713 | double ThetaG_JD(double jd) 714 | { 715 | /* Reference: The 1992 Astronomical Almanac, page B6. */ 716 | 717 | double UT, TU, GMST; 718 | 719 | UT=Frac(jd+0.5); 720 | jd=jd-UT; 721 | TU=(jd-2451545.0)/36525; 722 | GMST=24110.54841+TU*(8640184.812866+TU*(0.093104-TU*6.2E-6)); 723 | GMST=Modulus(GMST+secday*omega_E*UT,secday); 724 | 725 | return (twopi*GMST/secday); 726 | } 727 | 728 | void Calculate_Solar_Position(double time, vector_t *solar_vector) 729 | { 730 | /* Calculates solar position vector */ 731 | 732 | double mjd, year, T, M, L, e, C, O, Lsa, nu, R, eps; 733 | 734 | mjd=time-2415020.0; 735 | year=1900+mjd/365.25; 736 | T=(mjd+Delta_ET(year)/secday)/36525.0; 737 | M=Radians(Modulus(358.47583+Modulus(35999.04975*T,360.0)-(0.000150+0.0000033*T)*Sqr(T),360.0)); 738 | L=Radians(Modulus(279.69668+Modulus(36000.76892*T,360.0)+0.0003025*Sqr(T),360.0)); 739 | e=0.01675104-(0.0000418+0.000000126*T)*T; 740 | C=Radians((1.919460-(0.004789+0.000014*T)*T)*sin(M)+(0.020094-0.000100*T)*sin(2*M)+0.000293*sin(3*M)); 741 | O=Radians(Modulus(259.18-1934.142*T,360.0)); 742 | Lsa=Modulus(L+C-Radians(0.00569-0.00479*sin(O)),twopi); 743 | nu=Modulus(M+C,twopi); 744 | R=1.0000002*(1.0-Sqr(e))/(1.0+e*cos(nu)); 745 | eps=Radians(23.452294-(0.0130125+(0.00000164-0.000000503*T)*T)*T+0.00256*cos(O)); 746 | R=AU*R; 747 | solar_vector->x=R*cos(Lsa); 748 | solar_vector->y=R*sin(Lsa)*cos(eps); 749 | solar_vector->z=R*sin(Lsa)*sin(eps); 750 | solar_vector->w=R; 751 | } 752 | 753 | int Sat_Eclipsed(vector_t *pos, vector_t *sol, double *depth) 754 | { 755 | /* Calculates satellite's eclipse status and depth */ 756 | 757 | double sd_sun, sd_earth, delta; 758 | vector_t Rho, earth; 759 | 760 | /* Determine partial eclipse */ 761 | 762 | sd_earth=ArcSin(xkmper/pos->w); 763 | Vec_Sub(sol,pos,&Rho); 764 | sd_sun=ArcSin(sr/Rho.w); 765 | Scalar_Multiply(-1,pos,&earth); 766 | delta=Angle(sol,&earth); 767 | *depth=sd_earth-sd_sun-delta; 768 | 769 | if (sd_earth=0) 774 | { 775 | return 1; 776 | } 777 | else 778 | { 779 | return 0; 780 | } 781 | } 782 | 783 | void select_ephemeris(tle_t *tle) 784 | { 785 | /* Selects the apropriate ephemeris type to be used */ 786 | /* for predictions according to the data in the TLE */ 787 | /* It also processes values in the tle set so that */ 788 | /* they are apropriate for the sgp4/sdp4 routines */ 789 | 790 | double ao, xnodp, dd1, dd2, delo, temp, a1, del1, r1; 791 | 792 | /* Preprocess tle set */ 793 | tle->xnodeo*=deg2rad; 794 | tle->omegao*=deg2rad; 795 | tle->xmo*=deg2rad; 796 | tle->xincl*=deg2rad; 797 | temp=twopi/xmnpda/xmnpda; 798 | tle->xno=tle->xno*temp*xmnpda; 799 | tle->xndt2o*=temp; 800 | tle->xndd6o=tle->xndd6o*temp/xmnpda; 801 | tle->bstar/=ae; 802 | 803 | /* Period > 225 minutes is deep space */ 804 | dd1=(xke/tle->xno); 805 | dd2=tothrd; 806 | a1=pow(dd1,dd2); 807 | r1=cos(tle->xincl); 808 | dd1=(1.0-tle->eo*tle->eo); 809 | temp=ck2*1.5f*(r1*r1*3.0-1.0)/pow(dd1,1.5); 810 | del1=temp/(a1*a1); 811 | ao=a1*(1.0-del1*(tothrd*.5+del1*(del1*1.654320987654321+1.0))); 812 | delo=temp/(ao*ao); 813 | xnodp=tle->xno/(delo+1.0); 814 | 815 | /* Select a deep-space/near-earth ephemeris */ 816 | 817 | if (twopi/xnodp/xmnpda>=0.15625) 818 | { 819 | SetFlag(DEEP_SPACE_EPHEM_FLAG); 820 | } 821 | else 822 | { 823 | ClearFlag(DEEP_SPACE_EPHEM_FLAG); 824 | } 825 | } 826 | 827 | void SGP4(double tsince, tle_t * tle, vector_t * pos, vector_t * vel) 828 | { 829 | /* This function is used to calculate the position and velocity */ 830 | /* of near-earth (period < 225 minutes) satellites. tsince is */ 831 | /* time since epoch in minutes, tle is a pointer to a tle_t */ 832 | /* structure with Keplerian orbital elements and pos and vel */ 833 | /* are vector_t structures returning ECI satellite position and */ 834 | /* velocity. Use Convert_Sat_State() to convert to km and km/s. */ 835 | 836 | static double aodp, aycof, c1, c4, c5, cosio, d2, d3, d4, delmo, 837 | omgcof, eta, omgdot, sinio, xnodp, sinmo, t2cof, t3cof, t4cof, 838 | t5cof, x1mth2, x3thm1, x7thm1, xmcof, xmdot, xnodcf, xnodot, xlcof; 839 | 840 | double cosuk, sinuk, rfdotk, vx, vy, vz, ux, uy, uz, xmy, xmx, cosnok, 841 | sinnok, cosik, sinik, rdotk, xinck, xnodek, uk, rk, cos2u, sin2u, 842 | u, sinu, cosu, betal, rfdot, rdot, r, pl, elsq, esine, ecose, epw, 843 | cosepw, x1m5th, xhdot1, tfour, sinepw, capu, ayn, xlt, aynl, xll, 844 | axn, xn, beta, xl, e, a, tcube, delm, delomg, templ, tempe, tempa, 845 | xnode, tsq, xmp, omega, xnoddf, omgadf, xmdf, a1, a3ovk2, ao, 846 | betao, betao2, c1sq, c2, c3, coef, coef1, del1, delo, eeta, eosq, 847 | etasq, perigee, pinvsq, psisq, qoms24, s4, temp, temp1, temp2, 848 | temp3, temp4, temp5, temp6, theta2, theta4, tsi; 849 | 850 | int i; 851 | 852 | /* Initialization */ 853 | 854 | if (isFlagClear(SGP4_INITIALIZED_FLAG)) 855 | { 856 | SetFlag(SGP4_INITIALIZED_FLAG); 857 | 858 | /* Recover original mean motion (xnodp) and */ 859 | /* semimajor axis (aodp) from input elements. */ 860 | 861 | a1=pow(xke/tle->xno,tothrd); 862 | cosio=cos(tle->xincl); 863 | theta2=cosio*cosio; 864 | x3thm1=3*theta2-1.0; 865 | eosq=tle->eo*tle->eo; 866 | betao2=1.0-eosq; 867 | betao=sqrt(betao2); 868 | del1=1.5*ck2*x3thm1/(a1*a1*betao*betao2); 869 | ao=a1*(1.0-del1*(0.5*tothrd+del1*(1.0+134.0/81.0*del1))); 870 | delo=1.5*ck2*x3thm1/(ao*ao*betao*betao2); 871 | xnodp=tle->xno/(1.0+delo); 872 | aodp=ao/(1.0-delo); 873 | 874 | /* For perigee less than 220 kilometers, the "simple" */ 875 | /* flag is set and the equations are truncated to linear */ 876 | /* variation in sqrt a and quadratic variation in mean */ 877 | /* anomaly. Also, the c3 term, the delta omega term, and */ 878 | /* the delta m term are dropped. */ 879 | 880 | if ((aodp*(1-tle->eo)/ae)<(220/xkmper+ae)) 881 | { 882 | SetFlag(SIMPLE_FLAG); 883 | } 884 | 885 | else 886 | { 887 | ClearFlag(SIMPLE_FLAG); 888 | } 889 | 890 | /* For perigees below 156 km, the */ 891 | /* values of s and qoms2t are altered. */ 892 | 893 | s4=s; 894 | qoms24=qoms2t; 895 | perigee=(aodp*(1-tle->eo)-ae)*xkmper; 896 | 897 | if (perigee<156.0) 898 | { 899 | if (perigee<=98.0) 900 | { 901 | s4=20; 902 | } 903 | else 904 | { 905 | s4=perigee-78.0; 906 | } 907 | 908 | qoms24=pow((120-s4)*ae/xkmper,4); 909 | s4=s4/xkmper+ae; 910 | } 911 | 912 | pinvsq=1/(aodp*aodp*betao2*betao2); 913 | tsi=1/(aodp-s4); 914 | eta=aodp*tle->eo*tsi; 915 | etasq=eta*eta; 916 | eeta=tle->eo*eta; 917 | psisq=fabs(1-etasq); 918 | coef=qoms24*pow(tsi,4); 919 | coef1=coef/pow(psisq,3.5); 920 | c2=coef1*xnodp*(aodp*(1+1.5*etasq+eeta*(4+etasq))+0.75*ck2*tsi/psisq*x3thm1*(8+3*etasq*(8+etasq))); 921 | c1=tle->bstar*c2; 922 | sinio=sin(tle->xincl); 923 | a3ovk2=-xj3/ck2*pow(ae,3); 924 | c3=coef*tsi*a3ovk2*xnodp*ae*sinio/tle->eo; 925 | x1mth2=1-theta2; 926 | 927 | c4=2*xnodp*coef1*aodp*betao2*(eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*tsi/(aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*(1.5-0.5*eeta))+0.75*x1mth2*(2*etasq-eeta*(1+etasq))*cos(2*tle->omegao))); 928 | c5=2*coef1*aodp*betao2*(1+2.75*(etasq+eeta)+eeta*etasq); 929 | 930 | theta4=theta2*theta2; 931 | temp1=3*ck2*pinvsq*xnodp; 932 | temp2=temp1*ck2*pinvsq; 933 | temp3=1.25*ck4*pinvsq*pinvsq*xnodp; 934 | xmdot=xnodp+0.5*temp1*betao*x3thm1+0.0625*temp2*betao*(13-78*theta2+137*theta4); 935 | x1m5th=1-5*theta2; 936 | omgdot=-0.5*temp1*x1m5th+0.0625*temp2*(7-114*theta2+395*theta4)+temp3*(3-36*theta2+49*theta4); 937 | xhdot1=-temp1*cosio; 938 | xnodot=xhdot1+(0.5*temp2*(4-19*theta2)+2*temp3*(3-7*theta2))*cosio; 939 | omgcof=tle->bstar*c3*cos(tle->omegao); 940 | xmcof=-tothrd*coef*tle->bstar*ae/eeta; 941 | xnodcf=3.5*betao2*xhdot1*c1; 942 | t2cof=1.5*c1; 943 | xlcof=0.125*a3ovk2*sinio*(3+5*cosio)/(1+cosio); 944 | aycof=0.25*a3ovk2*sinio; 945 | delmo=pow(1+eta*cos(tle->xmo),3); 946 | sinmo=sin(tle->xmo); 947 | x7thm1=7*theta2-1; 948 | 949 | if (isFlagClear(SIMPLE_FLAG)) 950 | { 951 | c1sq=c1*c1; 952 | d2=4*aodp*tsi*c1sq; 953 | temp=d2*tsi*c1/3; 954 | d3=(17*aodp+s4)*temp; 955 | d4=0.5*temp*aodp*tsi*(221*aodp+31*s4)*c1; 956 | t3cof=d2+2*c1sq; 957 | t4cof=0.25*(3*d3+c1*(12*d2+10*c1sq)); 958 | t5cof=0.2*(3*d4+12*c1*d3+6*d2*d2+15*c1sq*(2*d2+c1sq)); 959 | } 960 | } 961 | 962 | /* Update for secular gravity and atmospheric drag. */ 963 | xmdf=tle->xmo+xmdot*tsince; 964 | omgadf=tle->omegao+omgdot*tsince; 965 | xnoddf=tle->xnodeo+xnodot*tsince; 966 | omega=omgadf; 967 | xmp=xmdf; 968 | tsq=tsince*tsince; 969 | xnode=xnoddf+xnodcf*tsq; 970 | tempa=1-c1*tsince; 971 | tempe=tle->bstar*c4*tsince; 972 | templ=t2cof*tsq; 973 | 974 | if (isFlagClear(SIMPLE_FLAG)) 975 | { 976 | delomg=omgcof*tsince; 977 | delm=xmcof*(pow(1+eta*cos(xmdf),3)-delmo); 978 | temp=delomg+delm; 979 | xmp=xmdf+temp; 980 | omega=omgadf-temp; 981 | tcube=tsq*tsince; 982 | tfour=tsince*tcube; 983 | tempa=tempa-d2*tsq-d3*tcube-d4*tfour; 984 | tempe=tempe+tle->bstar*c5*(sin(xmp)-sinmo); 985 | templ=templ+t3cof*tcube+tfour*(t4cof+tsince*t5cof); 986 | } 987 | 988 | a=aodp*pow(tempa,2); 989 | e=tle->eo-tempe; 990 | xl=xmp+omega+xnode+xnodp*templ; 991 | beta=sqrt(1-e*e); 992 | xn=xke/pow(a,1.5); 993 | 994 | /* Long period periodics */ 995 | axn=e*cos(omega); 996 | temp=1/(a*beta*beta); 997 | xll=temp*xlcof*axn; 998 | aynl=temp*aycof; 999 | xlt=xl+xll; 1000 | ayn=e*sin(omega)+aynl; 1001 | 1002 | /* Solve Kepler's Equation */ 1003 | capu=FMod2p(xlt-xnode); 1004 | temp2=capu; 1005 | i=0; 1006 | 1007 | do 1008 | { 1009 | sinepw=sin(temp2); 1010 | cosepw=cos(temp2); 1011 | temp3=axn*sinepw; 1012 | temp4=ayn*cosepw; 1013 | temp5=axn*cosepw; 1014 | temp6=ayn*sinepw; 1015 | epw=(capu-temp4+temp3-temp2)/(1-temp5-temp6)+temp2; 1016 | 1017 | if (fabs(epw-temp2)<= e6a) 1018 | { 1019 | break; 1020 | } 1021 | 1022 | temp2=epw; 1023 | 1024 | } while (i++<10); 1025 | 1026 | /* Short period preliminary quantities */ 1027 | ecose=temp5+temp6; 1028 | esine=temp3-temp4; 1029 | elsq=axn*axn+ayn*ayn; 1030 | temp=1-elsq; 1031 | pl=a*temp; 1032 | r=a*(1-ecose); 1033 | temp1=1/r; 1034 | rdot=xke*sqrt(a)*esine*temp1; 1035 | rfdot=xke*sqrt(pl)*temp1; 1036 | temp2=a*temp1; 1037 | betal=sqrt(temp); 1038 | temp3=1/(1+betal); 1039 | cosu=temp2*(cosepw-axn+ayn*esine*temp3); 1040 | sinu=temp2*(sinepw-ayn-axn*esine*temp3); 1041 | u=AcTan(sinu,cosu); 1042 | sin2u=2*sinu*cosu; 1043 | cos2u=2*cosu*cosu-1; 1044 | temp=1/pl; 1045 | temp1=ck2*temp; 1046 | temp2=temp1*temp; 1047 | 1048 | /* Update for short periodics */ 1049 | rk=r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*x1mth2*cos2u; 1050 | uk=u-0.25*temp2*x7thm1*sin2u; 1051 | xnodek=xnode+1.5*temp2*cosio*sin2u; 1052 | xinck=tle->xincl+1.5*temp2*cosio*sinio*cos2u; 1053 | rdotk=rdot-xn*temp1*x1mth2*sin2u; 1054 | rfdotk=rfdot+xn*temp1*(x1mth2*cos2u+1.5*x3thm1); 1055 | 1056 | /* Orientation vectors */ 1057 | sinuk=sin(uk); 1058 | cosuk=cos(uk); 1059 | sinik=sin(xinck); 1060 | cosik=cos(xinck); 1061 | sinnok=sin(xnodek); 1062 | cosnok=cos(xnodek); 1063 | xmx=-sinnok*cosik; 1064 | xmy=cosnok*cosik; 1065 | ux=xmx*sinuk+cosnok*cosuk; 1066 | uy=xmy*sinuk+sinnok*cosuk; 1067 | uz=sinik*sinuk; 1068 | vx=xmx*cosuk-cosnok*sinuk; 1069 | vy=xmy*cosuk-sinnok*sinuk; 1070 | vz=sinik*cosuk; 1071 | 1072 | /* Position and velocity */ 1073 | pos->x=rk*ux; 1074 | pos->y=rk*uy; 1075 | pos->z=rk*uz; 1076 | vel->x=rdotk*ux+rfdotk*vx; 1077 | vel->y=rdotk*uy+rfdotk*vy; 1078 | vel->z=rdotk*uz+rfdotk*vz; 1079 | 1080 | /* Phase in radians */ 1081 | phase=xlt-xnode-omgadf+twopi; 1082 | 1083 | if (phase<0.0) 1084 | { 1085 | phase+=twopi; 1086 | } 1087 | 1088 | phase=FMod2p(phase); 1089 | } 1090 | 1091 | void Deep(int ientry, tle_t * tle, deep_arg_t * deep_arg) 1092 | { 1093 | /* This function is used by SDP4 to add lunar and solar */ 1094 | /* perturbation effects to deep-space orbit objects. */ 1095 | 1096 | static double thgr, xnq, xqncl, omegaq, zmol, zmos, savtsn, ee2, e3, 1097 | xi2, xl2, xl3, xl4, xgh2, xgh3, xgh4, xh2, xh3, sse, ssi, ssg, xi3, 1098 | se2, si2, sl2, sgh2, sh2, se3, si3, sl3, sgh3, sh3, sl4, sgh4, ssl, 1099 | ssh, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433, del1, 1100 | del2, del3, fasx2, fasx4, fasx6, xlamo, xfact, xni, atime, stepp, 1101 | stepn, step2, preep, pl, sghs, xli, d2201, d2211, sghl, sh1, pinc, 1102 | pe, shs, zsingl, zcosgl, zsinhl, zcoshl, zsinil, zcosil; 1103 | 1104 | double a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ainv2, alfdp, aqnv, 1105 | sgh, sini2, sinis, sinok, sh, si, sil, day, betdp, dalf, bfact, c, 1106 | cc, cosis, cosok, cosq, ctem, f322, zx, zy, dbet, dls, eoc, eq, f2, 1107 | f220, f221, f3, f311, f321, xnoh, f330, f441, f442, f522, f523, 1108 | f542, f543, g200, g201, g211, pgh, ph, s1, s2, s3, s4, s5, s6, s7, 1109 | se, sel, ses, xls, g300, g310, g322, g410, g422, g520, g521, g532, 1110 | g533, gam, sinq, sinzf, sis, sl, sll, sls, stem, temp, temp1, x1, 1111 | x2, x2li, x2omi, x3, x4, x5, x6, x7, x8, xl, xldot, xmao, xnddt, 1112 | xndot, xno2, xnodce, xnoi, xomi, xpidot, z1, z11, z12, z13, z2, 1113 | z21, z22, z23, z3, z31, z32, z33, ze, zf, zm, zmo, zn, zsing, 1114 | zsinh, zsini, zcosg, zcosh, zcosi, delt=0, ft=0; 1115 | 1116 | switch (ientry) 1117 | { 1118 | case dpinit: /* Entrance for deep space initialization */ 1119 | thgr=ThetaG(tle->epoch,deep_arg); 1120 | eq=tle->eo; 1121 | xnq=deep_arg->xnodp; 1122 | aqnv=1/deep_arg->aodp; 1123 | xqncl=tle->xincl; 1124 | xmao=tle->xmo; 1125 | xpidot=deep_arg->omgdot+deep_arg->xnodot; 1126 | sinq=sin(tle->xnodeo); 1127 | cosq=cos(tle->xnodeo); 1128 | omegaq=tle->omegao; 1129 | 1130 | /* Initialize lunar solar terms */ 1131 | day=deep_arg->ds50+18261.5; /* Days since 1900 Jan 0.5 */ 1132 | 1133 | if (day!=preep) 1134 | { 1135 | preep=day; 1136 | xnodce=4.5236020-9.2422029E-4*day; 1137 | stem=sin(xnodce); 1138 | ctem=cos(xnodce); 1139 | zcosil=0.91375164-0.03568096*ctem; 1140 | zsinil=sqrt(1-zcosil*zcosil); 1141 | zsinhl=0.089683511*stem/zsinil; 1142 | zcoshl=sqrt(1-zsinhl*zsinhl); 1143 | c=4.7199672+0.22997150*day; 1144 | gam=5.8351514+0.0019443680*day; 1145 | zmol=FMod2p(c-gam); 1146 | zx=0.39785416*stem/zsinil; 1147 | zy=zcoshl*ctem+0.91744867*zsinhl*stem; 1148 | zx=AcTan(zx,zy); 1149 | zx=gam+zx-xnodce; 1150 | zcosgl=cos(zx); 1151 | zsingl=sin(zx); 1152 | zmos=6.2565837+0.017201977*day; 1153 | zmos=FMod2p(zmos); 1154 | } 1155 | 1156 | /* Do solar terms */ 1157 | savtsn=1E20; 1158 | zcosg=zcosgs; 1159 | zsing=zsings; 1160 | zcosi=zcosis; 1161 | zsini=zsinis; 1162 | zcosh=cosq; 1163 | zsinh= sinq; 1164 | cc=c1ss; 1165 | zn=zns; 1166 | ze=zes; 1167 | zmo=zmos; 1168 | xnoi=1/xnq; 1169 | 1170 | /* Loop breaks when Solar terms are done a second */ 1171 | /* time, after Lunar terms are initialized */ 1172 | 1173 | for (;;) 1174 | { 1175 | /* Solar terms done again after Lunar terms are done */ 1176 | a1=zcosg*zcosh+zsing*zcosi*zsinh; 1177 | a3=-zsing*zcosh+zcosg*zcosi*zsinh; 1178 | a7=-zcosg*zsinh+zsing*zcosi*zcosh; 1179 | a8=zsing*zsini; 1180 | a9=zsing*zsinh+zcosg*zcosi*zcosh; 1181 | a10=zcosg*zsini; 1182 | a2=deep_arg->cosio*a7+deep_arg->sinio*a8; 1183 | a4=deep_arg->cosio*a9+deep_arg->sinio*a10; 1184 | a5=-deep_arg->sinio*a7+deep_arg->cosio*a8; 1185 | a6=-deep_arg->sinio*a9+deep_arg->cosio*a10; 1186 | x1=a1*deep_arg->cosg+a2*deep_arg->sing; 1187 | x2=a3*deep_arg->cosg+a4*deep_arg->sing; 1188 | x3=-a1*deep_arg->sing+a2*deep_arg->cosg; 1189 | x4=-a3*deep_arg->sing+a4*deep_arg->cosg; 1190 | x5=a5*deep_arg->sing; 1191 | x6=a6*deep_arg->sing; 1192 | x7=a5*deep_arg->cosg; 1193 | x8=a6*deep_arg->cosg; 1194 | z31=12*x1*x1-3*x3*x3; 1195 | z32=24*x1*x2-6*x3*x4; 1196 | z33=12*x2*x2-3*x4*x4; 1197 | z1=3*(a1*a1+a2*a2)+z31*deep_arg->eosq; 1198 | z2=6*(a1*a3+a2*a4)+z32*deep_arg->eosq; 1199 | z3=3*(a3*a3+a4*a4)+z33*deep_arg->eosq; 1200 | z11=-6*a1*a5+deep_arg->eosq*(-24*x1*x7-6*x3*x5); 1201 | z12=-6*(a1*a6+a3*a5)+deep_arg->eosq*(-24*(x2*x7+x1*x8)-6*(x3*x6+x4*x5)); 1202 | z13=-6*a3*a6+deep_arg->eosq*(-24*x2*x8-6*x4*x6); 1203 | z21=6*a2*a5+deep_arg->eosq*(24*x1*x5-6*x3*x7); 1204 | z22=6*(a4*a5+a2*a6)+deep_arg->eosq*(24*(x2*x5+x1*x6)-6*(x4*x7+x3*x8)); 1205 | z23=6*a4*a6+deep_arg->eosq*(24*x2*x6-6*x4*x8); 1206 | z1=z1+z1+deep_arg->betao2*z31; 1207 | z2=z2+z2+deep_arg->betao2*z32; 1208 | z3=z3+z3+deep_arg->betao2*z33; 1209 | s3=cc*xnoi; 1210 | s2=-0.5*s3/deep_arg->betao; 1211 | s4=s3*deep_arg->betao; 1212 | s1=-15*eq*s4; 1213 | s5=x1*x3+x2*x4; 1214 | s6=x2*x3+x1*x4; 1215 | s7=x2*x4-x1*x3; 1216 | se=s1*zn*s5; 1217 | si=s2*zn*(z11+z13); 1218 | sl=-zn*s3*(z1+z3-14-6*deep_arg->eosq); 1219 | sgh=s4*zn*(z31+z33-6); 1220 | sh=-zn*s2*(z21+z23); 1221 | 1222 | if (xqncl<5.2359877E-2) 1223 | { 1224 | sh=0; 1225 | } 1226 | 1227 | ee2=2*s1*s6; 1228 | e3=2*s1*s7; 1229 | xi2=2*s2*z12; 1230 | xi3=2*s2*(z13-z11); 1231 | xl2=-2*s3*z2; 1232 | xl3=-2*s3*(z3-z1); 1233 | xl4=-2*s3*(-21-9*deep_arg->eosq)*ze; 1234 | xgh2=2*s4*z32; 1235 | xgh3=2*s4*(z33-z31); 1236 | xgh4=-18*s4*ze; 1237 | xh2=-2*s2*z22; 1238 | xh3=-2*s2*(z23-z21); 1239 | 1240 | if (isFlagSet(LUNAR_TERMS_DONE_FLAG)) 1241 | { 1242 | break; 1243 | } 1244 | 1245 | /* Do lunar terms */ 1246 | sse=se; 1247 | ssi=si; 1248 | ssl=sl; 1249 | ssh=sh/deep_arg->sinio; 1250 | ssg=sgh-deep_arg->cosio*ssh; 1251 | se2=ee2; 1252 | si2=xi2; 1253 | sl2=xl2; 1254 | sgh2=xgh2; 1255 | sh2=xh2; 1256 | se3=e3; 1257 | si3=xi3; 1258 | sl3=xl3; 1259 | sgh3=xgh3; 1260 | sh3=xh3; 1261 | sl4=xl4; 1262 | sgh4=xgh4; 1263 | zcosg=zcosgl; 1264 | zsing=zsingl; 1265 | zcosi=zcosil; 1266 | zsini=zsinil; 1267 | zcosh=zcoshl*cosq+zsinhl*sinq; 1268 | zsinh=sinq*zcoshl-cosq*zsinhl; 1269 | zn=znl; 1270 | cc=c1l; 1271 | ze=zel; 1272 | zmo=zmol; 1273 | SetFlag(LUNAR_TERMS_DONE_FLAG); 1274 | } 1275 | 1276 | sse=sse+se; 1277 | ssi=ssi+si; 1278 | ssl=ssl+sl; 1279 | ssg=ssg+sgh-deep_arg->cosio/deep_arg->sinio*sh; 1280 | ssh=ssh+sh/deep_arg->sinio; 1281 | 1282 | /* Geopotential resonance initialization for 12 hour orbits */ 1283 | ClearFlag(RESONANCE_FLAG); 1284 | ClearFlag(SYNCHRONOUS_FLAG); 1285 | 1286 | if (!((xnq<0.0052359877) && (xnq>0.0034906585))) 1287 | { 1288 | if ((xnq<0.00826) || (xnq>0.00924)) 1289 | { 1290 | return; 1291 | } 1292 | 1293 | if (eq<0.5) 1294 | { 1295 | return; 1296 | } 1297 | 1298 | SetFlag(RESONANCE_FLAG); 1299 | eoc=eq*deep_arg->eosq; 1300 | g201=-0.306-(eq-0.64)*0.440; 1301 | 1302 | if (eq<=0.65) 1303 | { 1304 | g211=3.616-13.247*eq+16.290*deep_arg->eosq; 1305 | g310=-19.302+117.390*eq-228.419*deep_arg->eosq+156.591*eoc; 1306 | g322=-18.9068+109.7927*eq-214.6334*deep_arg->eosq+146.5816*eoc; 1307 | g410=-41.122+242.694*eq-471.094*deep_arg->eosq+313.953*eoc; 1308 | g422=-146.407+841.880*eq-1629.014*deep_arg->eosq+1083.435 * eoc; 1309 | g520=-532.114+3017.977*eq-5740*deep_arg->eosq+3708.276*eoc; 1310 | } 1311 | 1312 | else 1313 | { 1314 | g211=-72.099+331.819*eq-508.738*deep_arg->eosq+266.724*eoc; 1315 | g310=-346.844+1582.851*eq-2415.925*deep_arg->eosq+1246.113*eoc; 1316 | g322=-342.585+1554.908*eq-2366.899*deep_arg->eosq+1215.972*eoc; 1317 | g410=-1052.797+4758.686*eq-7193.992*deep_arg->eosq+3651.957*eoc; 1318 | g422=-3581.69+16178.11*eq-24462.77*deep_arg->eosq+12422.52*eoc; 1319 | 1320 | if (eq<=0.715) 1321 | { 1322 | g520=1464.74-4664.75*eq+3763.64*deep_arg->eosq; 1323 | } 1324 | 1325 | else 1326 | { 1327 | g520=-5149.66+29936.92*eq-54087.36*deep_arg->eosq+31324.56*eoc; 1328 | } 1329 | } 1330 | 1331 | if (eq<0.7) 1332 | { 1333 | g533=-919.2277+4988.61*eq-9064.77*deep_arg->eosq+5542.21*eoc; 1334 | g521=-822.71072+4568.6173*eq-8491.4146*deep_arg->eosq+5337.524*eoc; 1335 | g532=-853.666+4690.25*eq-8624.77*deep_arg->eosq+5341.4*eoc; 1336 | } 1337 | 1338 | else 1339 | { 1340 | g533=-37995.78+161616.52*eq-229838.2*deep_arg->eosq+109377.94*eoc; 1341 | g521 =-51752.104+218913.95*eq-309468.16*deep_arg->eosq+146349.42*eoc; 1342 | g532 =-40023.88+170470.89*eq-242699.48*deep_arg->eosq+115605.82*eoc; 1343 | } 1344 | 1345 | sini2=deep_arg->sinio*deep_arg->sinio; 1346 | f220=0.75*(1+2*deep_arg->cosio+deep_arg->theta2); 1347 | f221=1.5*sini2; 1348 | f321=1.875*deep_arg->sinio*(1-2*deep_arg->cosio-3*deep_arg->theta2); 1349 | f322=-1.875*deep_arg->sinio*(1+2*deep_arg->cosio-3*deep_arg->theta2); 1350 | f441=35*sini2*f220; 1351 | f442=39.3750*sini2*sini2; 1352 | f522=9.84375*deep_arg->sinio*(sini2*(1-2*deep_arg->cosio-5*deep_arg->theta2)+0.33333333*(-2+4*deep_arg->cosio+6*deep_arg->theta2)); 1353 | f523=deep_arg->sinio*(4.92187512*sini2*(-2-4*deep_arg->cosio+10*deep_arg->theta2)+6.56250012*(1+2*deep_arg->cosio-3*deep_arg->theta2)); 1354 | f542=29.53125*deep_arg->sinio*(2-8*deep_arg->cosio+deep_arg->theta2*(-12+8*deep_arg->cosio+10*deep_arg->theta2)); 1355 | f543=29.53125*deep_arg->sinio*(-2-8*deep_arg->cosio+deep_arg->theta2*(12+8*deep_arg->cosio-10*deep_arg->theta2)); 1356 | xno2=xnq*xnq; 1357 | ainv2=aqnv*aqnv; 1358 | temp1=3*xno2*ainv2; 1359 | temp=temp1*root22; 1360 | d2201=temp*f220*g201; 1361 | d2211=temp*f221*g211; 1362 | temp1=temp1*aqnv; 1363 | temp=temp1*root32; 1364 | d3210=temp*f321*g310; 1365 | d3222=temp*f322*g322; 1366 | temp1=temp1*aqnv; 1367 | temp=2*temp1*root44; 1368 | d4410=temp*f441*g410; 1369 | d4422=temp*f442*g422; 1370 | temp1=temp1*aqnv; 1371 | temp=temp1*root52; 1372 | d5220=temp*f522*g520; 1373 | d5232=temp*f523*g532; 1374 | temp=2*temp1*root54; 1375 | d5421=temp*f542*g521; 1376 | d5433=temp*f543*g533; 1377 | xlamo=xmao+tle->xnodeo+tle->xnodeo-thgr-thgr; 1378 | bfact=deep_arg->xmdot+deep_arg->xnodot+deep_arg->xnodot-thdt-thdt; 1379 | bfact=bfact+ssl+ssh+ssh; 1380 | } 1381 | 1382 | else 1383 | { 1384 | SetFlag(RESONANCE_FLAG); 1385 | SetFlag(SYNCHRONOUS_FLAG); 1386 | 1387 | /* Synchronous resonance terms initialization */ 1388 | g200=1+deep_arg->eosq*(-2.5+0.8125*deep_arg->eosq); 1389 | g310=1+2*deep_arg->eosq; 1390 | g300=1+deep_arg->eosq*(-6+6.60937*deep_arg->eosq); 1391 | f220=0.75*(1+deep_arg->cosio)*(1+deep_arg->cosio); 1392 | f311=0.9375*deep_arg->sinio*deep_arg->sinio*(1+3*deep_arg->cosio)-0.75*(1+deep_arg->cosio); 1393 | f330=1+deep_arg->cosio; 1394 | f330=1.875*f330*f330*f330; 1395 | del1=3*xnq*xnq*aqnv*aqnv; 1396 | del2=2*del1*f220*g200*q22; 1397 | del3=3*del1*f330*g300*q33*aqnv; 1398 | del1=del1*f311*g310*q31*aqnv; 1399 | fasx2=0.13130908; 1400 | fasx4=2.8843198; 1401 | fasx6=0.37448087; 1402 | xlamo=xmao+tle->xnodeo+tle->omegao-thgr; 1403 | bfact=deep_arg->xmdot+xpidot-thdt; 1404 | bfact=bfact+ssl+ssg+ssh; 1405 | } 1406 | 1407 | xfact=bfact-xnq; 1408 | 1409 | /* Initialize integrator */ 1410 | xli=xlamo; 1411 | xni=xnq; 1412 | atime=0; 1413 | stepp=720; 1414 | stepn=-720; 1415 | step2=259200; 1416 | 1417 | return; 1418 | 1419 | case dpsec: /* Entrance for deep space secular effects */ 1420 | deep_arg->xll=deep_arg->xll+ssl*deep_arg->t; 1421 | deep_arg->omgadf=deep_arg->omgadf+ssg*deep_arg->t; 1422 | deep_arg->xnode=deep_arg->xnode+ssh*deep_arg->t; 1423 | deep_arg->em=tle->eo+sse*deep_arg->t; 1424 | deep_arg->xinc=tle->xincl+ssi*deep_arg->t; 1425 | 1426 | if (deep_arg->xinc<0) 1427 | { 1428 | deep_arg->xinc=-deep_arg->xinc; 1429 | deep_arg->xnode=deep_arg->xnode+pi; 1430 | deep_arg->omgadf=deep_arg->omgadf-pi; 1431 | } 1432 | 1433 | if (isFlagClear(RESONANCE_FLAG)) 1434 | { 1435 | return; 1436 | } 1437 | 1438 | do 1439 | { 1440 | if ((atime==0) || ((deep_arg->t>=0) && (atime<0)) || ((deep_arg->t<0) && (atime>=0))) 1441 | { 1442 | /* Epoch restart */ 1443 | 1444 | if (deep_arg->t>=0) 1445 | { 1446 | delt=stepp; 1447 | } 1448 | else 1449 | { 1450 | delt=stepn; 1451 | } 1452 | 1453 | atime=0; 1454 | xni=xnq; 1455 | xli=xlamo; 1456 | } 1457 | 1458 | else 1459 | { 1460 | if (fabs(deep_arg->t)>=fabs(atime)) 1461 | { 1462 | if (deep_arg->t>0) 1463 | { 1464 | delt=stepp; 1465 | } 1466 | else 1467 | { 1468 | delt=stepn; 1469 | } 1470 | } 1471 | } 1472 | 1473 | do 1474 | { 1475 | if (fabs(deep_arg->t-atime)>=stepp) 1476 | { 1477 | SetFlag(DO_LOOP_FLAG); 1478 | ClearFlag(EPOCH_RESTART_FLAG); 1479 | } 1480 | 1481 | else 1482 | { 1483 | ft=deep_arg->t-atime; 1484 | ClearFlag(DO_LOOP_FLAG); 1485 | } 1486 | 1487 | if (fabs(deep_arg->t)t>=0) 1490 | { 1491 | delt=stepn; 1492 | } 1493 | else 1494 | { 1495 | delt=stepp; 1496 | } 1497 | 1498 | SetFlag(DO_LOOP_FLAG | EPOCH_RESTART_FLAG); 1499 | } 1500 | 1501 | /* Dot terms calculated */ 1502 | if (isFlagSet(SYNCHRONOUS_FLAG)) 1503 | { 1504 | xndot=del1*sin(xli-fasx2)+del2*sin(2*(xli-fasx4))+del3*sin(3*(xli-fasx6)); 1505 | xnddt=del1*cos(xli-fasx2)+2*del2*cos(2*(xli-fasx4))+3*del3*cos(3*(xli-fasx6)); 1506 | } 1507 | 1508 | else 1509 | { 1510 | xomi=omegaq+deep_arg->omgdot*atime; 1511 | x2omi=xomi+xomi; 1512 | x2li=xli+xli; 1513 | xndot=d2201*sin(x2omi+xli-g22)+d2211*sin(xli-g22)+d3210*sin(xomi+xli-g32)+d3222*sin(-xomi+xli-g32)+d4410*sin(x2omi+x2li-g44)+d4422*sin(x2li-g44)+d5220*sin(xomi+xli-g52)+d5232*sin(-xomi+xli-g52)+d5421*sin(xomi+x2li-g54)+d5433*sin(-xomi+x2li-g54); 1514 | xnddt=d2201*cos(x2omi+xli-g22)+d2211*cos(xli-g22)+d3210*cos(xomi+xli-g32)+d3222*cos(-xomi+xli-g32)+d5220*cos(xomi+xli-g52)+d5232*cos(-xomi+xli-g52)+2*(d4410*cos(x2omi+x2li-g44)+d4422*cos(x2li-g44)+d5421*cos(xomi+x2li-g54)+d5433*cos(-xomi+x2li-g54)); 1515 | } 1516 | 1517 | xldot=xni+xfact; 1518 | xnddt=xnddt*xldot; 1519 | 1520 | if (isFlagSet(DO_LOOP_FLAG)) 1521 | { 1522 | xli=xli+xldot*delt+xndot*step2; 1523 | xni=xni+xndot*delt+xnddt*step2; 1524 | atime=atime+delt; 1525 | } 1526 | } while (isFlagSet(DO_LOOP_FLAG) && isFlagClear(EPOCH_RESTART_FLAG)); 1527 | } while (isFlagSet(DO_LOOP_FLAG) && isFlagSet(EPOCH_RESTART_FLAG)); 1528 | 1529 | deep_arg->xn=xni+xndot*ft+xnddt*ft*ft*0.5; 1530 | xl=xli+xldot*ft+xndot*ft*ft*0.5; 1531 | temp=-deep_arg->xnode+thgr+deep_arg->t*thdt; 1532 | 1533 | if (isFlagClear(SYNCHRONOUS_FLAG)) 1534 | { 1535 | deep_arg->xll=xl+temp+temp; 1536 | } 1537 | else 1538 | { 1539 | deep_arg->xll=xl-deep_arg->omgadf+temp; 1540 | } 1541 | 1542 | return; 1543 | 1544 | case dpper: /* Entrance for lunar-solar periodics */ 1545 | sinis=sin(deep_arg->xinc); 1546 | cosis=cos(deep_arg->xinc); 1547 | 1548 | if (fabs(savtsn-deep_arg->t)>=30) 1549 | { 1550 | savtsn=deep_arg->t; 1551 | zm=zmos+zns*deep_arg->t; 1552 | zf=zm+2*zes*sin(zm); 1553 | sinzf=sin(zf); 1554 | f2=0.5*sinzf*sinzf-0.25; 1555 | f3=-0.5*sinzf*cos(zf); 1556 | ses=se2*f2+se3*f3; 1557 | sis=si2*f2+si3*f3; 1558 | sls=sl2*f2+sl3*f3+sl4*sinzf; 1559 | sghs=sgh2*f2+sgh3*f3+sgh4*sinzf; 1560 | shs=sh2*f2+sh3*f3; 1561 | zm=zmol+znl*deep_arg->t; 1562 | zf=zm+2*zel*sin(zm); 1563 | sinzf=sin(zf); 1564 | f2=0.5*sinzf*sinzf-0.25; 1565 | f3=-0.5*sinzf*cos(zf); 1566 | sel=ee2*f2+e3*f3; 1567 | sil=xi2*f2+xi3*f3; 1568 | sll=xl2*f2+xl3*f3+xl4*sinzf; 1569 | sghl=xgh2*f2+xgh3*f3+xgh4*sinzf; 1570 | sh1=xh2*f2+xh3*f3; 1571 | pe=ses+sel; 1572 | pinc=sis+sil; 1573 | pl=sls+sll; 1574 | } 1575 | 1576 | pgh=sghs+sghl; 1577 | ph=shs+sh1; 1578 | deep_arg->xinc=deep_arg->xinc+pinc; 1579 | deep_arg->em=deep_arg->em+pe; 1580 | 1581 | if (xqncl>=0.2) 1582 | { 1583 | /* Apply periodics directly */ 1584 | ph=ph/deep_arg->sinio; 1585 | pgh=pgh-deep_arg->cosio*ph; 1586 | deep_arg->omgadf=deep_arg->omgadf+pgh; 1587 | deep_arg->xnode=deep_arg->xnode+ph; 1588 | deep_arg->xll=deep_arg->xll+pl; 1589 | } 1590 | 1591 | else 1592 | { 1593 | /* Apply periodics with Lyddane modification */ 1594 | sinok=sin(deep_arg->xnode); 1595 | cosok=cos(deep_arg->xnode); 1596 | alfdp=sinis*sinok; 1597 | betdp=sinis*cosok; 1598 | dalf=ph*cosok+pinc*cosis*sinok; 1599 | dbet=-ph*sinok+pinc*cosis*cosok; 1600 | alfdp=alfdp+dalf; 1601 | betdp=betdp+dbet; 1602 | deep_arg->xnode=FMod2p(deep_arg->xnode); 1603 | xls=deep_arg->xll+deep_arg->omgadf+cosis*deep_arg->xnode; 1604 | dls=pl+pgh-pinc*deep_arg->xnode*sinis; 1605 | xls=xls+dls; 1606 | xnoh=deep_arg->xnode; 1607 | deep_arg->xnode=AcTan(alfdp,betdp); 1608 | 1609 | /* This is a patch to Lyddane modification */ 1610 | /* suggested by Rob Matson. */ 1611 | 1612 | if (fabs(xnoh-deep_arg->xnode)>pi) 1613 | { 1614 | if (deep_arg->xnodexnode+=twopi; 1617 | } 1618 | else 1619 | { 1620 | deep_arg->xnode-=twopi; 1621 | } 1622 | } 1623 | 1624 | deep_arg->xll=deep_arg->xll+pl; 1625 | deep_arg->omgadf=xls-deep_arg->xll-cos(deep_arg->xinc)*deep_arg->xnode; 1626 | } 1627 | return; 1628 | } 1629 | } 1630 | 1631 | void SDP4(double tsince, tle_t * tle, vector_t * pos, vector_t * vel) 1632 | { 1633 | /* This function is used to calculate the position and velocity */ 1634 | /* of deep-space (period > 225 minutes) satellites. tsince is */ 1635 | /* time since epoch in minutes, tle is a pointer to a tle_t */ 1636 | /* structure with Keplerian orbital elements and pos and vel */ 1637 | /* are vector_t structures returning ECI satellite position and */ 1638 | /* velocity. Use Convert_Sat_State() to convert to km and km/s. */ 1639 | 1640 | int i; 1641 | 1642 | static double x3thm1, c1, x1mth2, c4, xnodcf, t2cof, xlcof, 1643 | aycof, x7thm1; 1644 | 1645 | double a, axn, ayn, aynl, beta, betal, capu, cos2u, cosepw, cosik, 1646 | cosnok, cosu, cosuk, ecose, elsq, epw, esine, pl, theta4, rdot, 1647 | rdotk, rfdot, rfdotk, rk, sin2u, sinepw, sinik, sinnok, sinu, 1648 | sinuk, tempe, templ, tsq, u, uk, ux, uy, uz, vx, vy, vz, xinck, xl, 1649 | xlt, xmam, xmdf, xmx, xmy, xnoddf, xnodek, xll, a1, a3ovk2, ao, c2, 1650 | coef, coef1, x1m5th, xhdot1, del1, r, delo, eeta, eta, etasq, 1651 | perigee, psisq, tsi, qoms24, s4, pinvsq, temp, tempa, temp1, 1652 | temp2, temp3, temp4, temp5, temp6, bx, by, bz, cx, cy, cz; 1653 | 1654 | static deep_arg_t deep_arg; 1655 | 1656 | /* Initialization */ 1657 | 1658 | if (isFlagClear(SDP4_INITIALIZED_FLAG)) 1659 | { 1660 | SetFlag(SDP4_INITIALIZED_FLAG); 1661 | 1662 | /* Recover original mean motion (xnodp) and */ 1663 | /* semimajor axis (aodp) from input elements. */ 1664 | 1665 | a1=pow(xke/tle->xno,tothrd); 1666 | deep_arg.cosio=cos(tle->xincl); 1667 | deep_arg.theta2=deep_arg.cosio*deep_arg.cosio; 1668 | x3thm1=3*deep_arg.theta2-1; 1669 | deep_arg.eosq=tle->eo*tle->eo; 1670 | deep_arg.betao2=1-deep_arg.eosq; 1671 | deep_arg.betao=sqrt(deep_arg.betao2); 1672 | del1=1.5*ck2*x3thm1/(a1*a1*deep_arg.betao*deep_arg.betao2); 1673 | ao=a1*(1-del1*(0.5*tothrd+del1*(1+134/81*del1))); 1674 | delo=1.5*ck2*x3thm1/(ao*ao*deep_arg.betao*deep_arg.betao2); 1675 | deep_arg.xnodp=tle->xno/(1+delo); 1676 | deep_arg.aodp=ao/(1-delo); 1677 | 1678 | /* For perigee below 156 km, the values */ 1679 | /* of s and qoms2t are altered. */ 1680 | 1681 | s4=s; 1682 | qoms24=qoms2t; 1683 | perigee=(deep_arg.aodp*(1-tle->eo)-ae)*xkmper; 1684 | 1685 | if (perigee<156.0) 1686 | { 1687 | if (perigee<=98.0) 1688 | { 1689 | s4=20.0; 1690 | } 1691 | else 1692 | { 1693 | s4=perigee-78.0; 1694 | } 1695 | 1696 | qoms24=pow((120-s4)*ae/xkmper,4); 1697 | s4=s4/xkmper+ae; 1698 | } 1699 | 1700 | pinvsq=1/(deep_arg.aodp*deep_arg.aodp*deep_arg.betao2*deep_arg.betao2); 1701 | deep_arg.sing=sin(tle->omegao); 1702 | deep_arg.cosg=cos(tle->omegao); 1703 | tsi=1/(deep_arg.aodp-s4); 1704 | eta=deep_arg.aodp*tle->eo*tsi; 1705 | etasq=eta*eta; 1706 | eeta=tle->eo*eta; 1707 | psisq=fabs(1-etasq); 1708 | coef=qoms24*pow(tsi,4); 1709 | coef1=coef/pow(psisq,3.5); 1710 | c2=coef1*deep_arg.xnodp*(deep_arg.aodp*(1+1.5*etasq+eeta*(4+etasq))+0.75*ck2*tsi/psisq*x3thm1*(8+3*etasq*(8+etasq))); 1711 | c1=tle->bstar*c2; 1712 | deep_arg.sinio=sin(tle->xincl); 1713 | a3ovk2=-xj3/ck2*pow(ae,3); 1714 | x1mth2=1-deep_arg.theta2; 1715 | c4=2*deep_arg.xnodp*coef1*deep_arg.aodp*deep_arg.betao2*(eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*tsi/(deep_arg.aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*(1.5-0.5*eeta))+0.75*x1mth2*(2*etasq-eeta*(1+etasq))*cos(2*tle->omegao))); 1716 | theta4=deep_arg.theta2*deep_arg.theta2; 1717 | temp1=3*ck2*pinvsq*deep_arg.xnodp; 1718 | temp2=temp1*ck2*pinvsq; 1719 | temp3=1.25*ck4*pinvsq*pinvsq*deep_arg.xnodp; 1720 | deep_arg.xmdot=deep_arg.xnodp+0.5*temp1*deep_arg.betao*x3thm1+0.0625*temp2*deep_arg.betao*(13-78*deep_arg.theta2+137*theta4); 1721 | x1m5th=1-5*deep_arg.theta2; 1722 | deep_arg.omgdot=-0.5*temp1*x1m5th+0.0625*temp2*(7-114*deep_arg.theta2+395*theta4)+temp3*(3-36*deep_arg.theta2+49*theta4); 1723 | xhdot1=-temp1*deep_arg.cosio; 1724 | deep_arg.xnodot=xhdot1+(0.5*temp2*(4-19*deep_arg.theta2)+2*temp3*(3-7*deep_arg.theta2))*deep_arg.cosio; 1725 | xnodcf=3.5*deep_arg.betao2*xhdot1*c1; 1726 | t2cof=1.5*c1; 1727 | xlcof=0.125*a3ovk2*deep_arg.sinio*(3+5*deep_arg.cosio)/(1+deep_arg.cosio); 1728 | aycof=0.25*a3ovk2*deep_arg.sinio; 1729 | x7thm1=7*deep_arg.theta2-1; 1730 | 1731 | /* initialize Deep() */ 1732 | 1733 | Deep(dpinit,tle,&deep_arg); 1734 | } 1735 | 1736 | /* Update for secular gravity and atmospheric drag */ 1737 | xmdf=tle->xmo+deep_arg.xmdot*tsince; 1738 | deep_arg.omgadf=tle->omegao+deep_arg.omgdot*tsince; 1739 | xnoddf=tle->xnodeo+deep_arg.xnodot*tsince; 1740 | tsq=tsince*tsince; 1741 | deep_arg.xnode=xnoddf+xnodcf*tsq; 1742 | tempa=1-c1*tsince; 1743 | tempe=tle->bstar*c4*tsince; 1744 | templ=t2cof*tsq; 1745 | deep_arg.xn=deep_arg.xnodp; 1746 | 1747 | /* Update for deep-space secular effects */ 1748 | deep_arg.xll=xmdf; 1749 | deep_arg.t=tsince; 1750 | 1751 | Deep(dpsec, tle, &deep_arg); 1752 | 1753 | xmdf=deep_arg.xll; 1754 | a=pow(xke/deep_arg.xn,tothrd)*tempa*tempa; 1755 | deep_arg.em=deep_arg.em-tempe; 1756 | xmam=xmdf+deep_arg.xnodp*templ; 1757 | 1758 | /* Update for deep-space periodic effects */ 1759 | deep_arg.xll=xmam; 1760 | 1761 | Deep(dpper,tle,&deep_arg); 1762 | 1763 | xmam=deep_arg.xll; 1764 | xl=xmam+deep_arg.omgadf+deep_arg.xnode; 1765 | beta=sqrt(1-deep_arg.em*deep_arg.em); 1766 | deep_arg.xn=xke/pow(a,1.5); 1767 | 1768 | /* Long period periodics */ 1769 | axn=deep_arg.em*cos(deep_arg.omgadf); 1770 | temp=1/(a*beta*beta); 1771 | xll=temp*xlcof*axn; 1772 | aynl=temp*aycof; 1773 | xlt=xl+xll; 1774 | ayn=deep_arg.em*sin(deep_arg.omgadf)+aynl; 1775 | 1776 | /* Solve Kepler's Equation */ 1777 | capu=FMod2p(xlt-deep_arg.xnode); 1778 | temp2=capu; 1779 | i=0; 1780 | 1781 | do 1782 | { 1783 | sinepw=sin(temp2); 1784 | cosepw=cos(temp2); 1785 | temp3=axn*sinepw; 1786 | temp4=ayn*cosepw; 1787 | temp5=axn*cosepw; 1788 | temp6=ayn*sinepw; 1789 | epw=(capu-temp4+temp3-temp2)/(1-temp5-temp6)+temp2; 1790 | 1791 | if (fabs(epw-temp2)<=e6a) 1792 | { 1793 | break; 1794 | } 1795 | 1796 | temp2=epw; 1797 | 1798 | } while (i++<10); 1799 | 1800 | /* Short period preliminary quantities */ 1801 | ecose=temp5+temp6; 1802 | esine=temp3-temp4; 1803 | elsq=axn*axn+ayn*ayn; 1804 | temp=1-elsq; 1805 | pl=a*temp; 1806 | r=a*(1-ecose); 1807 | temp1=1/r; 1808 | rdot=xke*sqrt(a)*esine*temp1; 1809 | rfdot=xke*sqrt(pl)*temp1; 1810 | temp2=a*temp1; 1811 | betal=sqrt(temp); 1812 | temp3=1/(1+betal); 1813 | cosu=temp2*(cosepw-axn+ayn*esine*temp3); 1814 | sinu=temp2*(sinepw-ayn-axn*esine*temp3); 1815 | u=AcTan(sinu,cosu); 1816 | sin2u=2*sinu*cosu; 1817 | cos2u=2*cosu*cosu-1; 1818 | temp=1/pl; 1819 | temp1=ck2*temp; 1820 | temp2=temp1*temp; 1821 | 1822 | /* Update for short periodics */ 1823 | rk=r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*x1mth2*cos2u; 1824 | uk=u-0.25*temp2*x7thm1*sin2u; 1825 | xnodek=deep_arg.xnode+1.5*temp2*deep_arg.cosio*sin2u; 1826 | xinck=deep_arg.xinc+1.5*temp2*deep_arg.cosio*deep_arg.sinio*cos2u; 1827 | rdotk=rdot-deep_arg.xn*temp1*x1mth2*sin2u; 1828 | rfdotk=rfdot+deep_arg.xn*temp1*(x1mth2*cos2u+1.5*x3thm1); 1829 | 1830 | /* Orientation vectors */ 1831 | sinuk=sin(uk); 1832 | cosuk=cos(uk); 1833 | sinik=sin(xinck); 1834 | cosik=cos(xinck); 1835 | sinnok=sin(xnodek); 1836 | cosnok=cos(xnodek); 1837 | xmx=-sinnok*cosik; 1838 | xmy=cosnok*cosik; 1839 | ux=xmx*sinuk+cosnok*cosuk; 1840 | uy=xmy*sinuk+sinnok*cosuk; 1841 | uz=sinik*sinuk; 1842 | vx=xmx*cosuk-cosnok*sinuk; 1843 | vy=xmy*cosuk-sinnok*sinuk; 1844 | vz=sinik*cosuk; 1845 | 1846 | /* Position and velocity */ 1847 | pos->x=rk*ux; 1848 | pos->y=rk*uy; 1849 | pos->z=rk*uz; 1850 | vel->x=rdotk*ux+rfdotk*vx; 1851 | vel->y=rdotk*uy+rfdotk*vy; 1852 | vel->z=rdotk*uz+rfdotk*vz; 1853 | 1854 | /* Calculations for squint angle begin here... */ 1855 | 1856 | if (calc_squint) 1857 | { 1858 | bx=cos(alat)*cos(alon+deep_arg.omgadf); 1859 | by=cos(alat)*sin(alon+deep_arg.omgadf); 1860 | bz=sin(alat); 1861 | cx=bx; 1862 | cy=by*cos(xinck)-bz*sin(xinck); 1863 | cz=by*sin(xinck)+bz*cos(xinck); 1864 | ax=cx*cos(xnodek)-cy*sin(xnodek); 1865 | ay=cx*sin(xnodek)+cy*cos(xnodek); 1866 | az=cz; 1867 | } 1868 | 1869 | /* Phase in radians */ 1870 | phase=xlt-deep_arg.xnode-deep_arg.omgadf+twopi; 1871 | 1872 | if (phase<0.0) 1873 | { 1874 | phase+=twopi; 1875 | } 1876 | 1877 | phase=FMod2p(phase); 1878 | } 1879 | 1880 | void Calculate_User_PosVel(double time, geodetic_t *geodetic, vector_t *obs_pos, vector_t *obs_vel) 1881 | { 1882 | /* Calculate_User_PosVel() passes the user's geodetic position 1883 | and the time of interest and returns the ECI position and 1884 | velocity of the observer. The velocity calculation assumes 1885 | the geodetic position is stationary relative to the earth's 1886 | surface. */ 1887 | 1888 | /* Reference: The 1992 Astronomical Almanac, page K11. */ 1889 | 1890 | double c, sq, achcp; 1891 | 1892 | geodetic->theta=FMod2p(ThetaG_JD(time)+geodetic->lon); /* LMST */ 1893 | c=1/sqrt(1+f*(f-2)*Sqr(sin(geodetic->lat))); 1894 | sq=Sqr(1-f)*c; 1895 | achcp=(xkmper*c+geodetic->alt)*cos(geodetic->lat); 1896 | obs_pos->x=achcp*cos(geodetic->theta); /* kilometers */ 1897 | obs_pos->y=achcp*sin(geodetic->theta); 1898 | obs_pos->z=(xkmper*sq+geodetic->alt)*sin(geodetic->lat); 1899 | obs_vel->x=-mfactor*obs_pos->y; /* kilometers/second */ 1900 | obs_vel->y=mfactor*obs_pos->x; 1901 | obs_vel->z=0; 1902 | Magnitude(obs_pos); 1903 | Magnitude(obs_vel); 1904 | } 1905 | 1906 | void Calculate_LatLonAlt(double time, vector_t *pos, geodetic_t *geodetic) 1907 | { 1908 | /* Procedure Calculate_LatLonAlt will calculate the geodetic */ 1909 | /* position of an object given its ECI position pos and time. */ 1910 | /* It is intended to be used to determine the ground track of */ 1911 | /* a satellite. The calculations assume the earth to be an */ 1912 | /* oblate spheroid as defined in WGS '72. */ 1913 | 1914 | /* Reference: The 1992 Astronomical Almanac, page K12. */ 1915 | 1916 | double r, e2, phi, c; 1917 | 1918 | geodetic->theta=AcTan(pos->y,pos->x); /* radians */ 1919 | geodetic->lon=FMod2p(geodetic->theta-ThetaG_JD(time)); /* radians */ 1920 | r=sqrt(Sqr(pos->x)+Sqr(pos->y)); 1921 | e2=f*(2-f); 1922 | geodetic->lat=AcTan(pos->z,r); /* radians */ 1923 | 1924 | do 1925 | { 1926 | phi=geodetic->lat; 1927 | c=1/sqrt(1-e2*Sqr(sin(phi))); 1928 | geodetic->lat=AcTan(pos->z+xkmper*c*e2*sin(phi),r); 1929 | 1930 | } while (fabs(geodetic->lat-phi)>=1E-10); 1931 | 1932 | geodetic->alt=r/cos(geodetic->lat)-xkmper*c; /* kilometers */ 1933 | 1934 | if (geodetic->lat>pio2) 1935 | { 1936 | geodetic->lat-=twopi; 1937 | } 1938 | } 1939 | 1940 | void Calculate_Obs(double time, vector_t *pos, vector_t *vel, geodetic_t *geodetic, vector_t *obs_set) 1941 | { 1942 | /* The procedures Calculate_Obs and Calculate_RADec calculate */ 1943 | /* the *topocentric* coordinates of the object with ECI position, */ 1944 | /* {pos}, and velocity, {vel}, from location {geodetic} at {time}. */ 1945 | /* The {obs_set} returned for Calculate_Obs consists of azimuth, */ 1946 | /* elevation, range, and range rate (in that order) with units of */ 1947 | /* radians, radians, kilometers, and kilometers/second, respectively. */ 1948 | /* The WGS '72 geoid is used and the effect of atmospheric refraction */ 1949 | /* (under standard temperature and pressure) is incorporated into the */ 1950 | /* elevation calculation; the effect of atmospheric refraction on */ 1951 | /* range and range rate has not yet been quantified. */ 1952 | 1953 | /* The {obs_set} for Calculate_RADec consists of right ascension and */ 1954 | /* declination (in that order) in radians. Again, calculations are */ 1955 | /* based on *topocentric* position using the WGS '72 geoid and */ 1956 | /* incorporating atmospheric refraction. */ 1957 | 1958 | double sin_lat, cos_lat, sin_theta, cos_theta, el, azim, top_s, top_e, top_z; 1959 | 1960 | vector_t obs_pos, obs_vel, range, rgvel; 1961 | 1962 | Calculate_User_PosVel(time, geodetic, &obs_pos, &obs_vel); 1963 | 1964 | range.x=pos->x-obs_pos.x; 1965 | range.y=pos->y-obs_pos.y; 1966 | range.z=pos->z-obs_pos.z; 1967 | 1968 | /* Save these values globally for calculating squint angles later... */ 1969 | 1970 | rx=range.x; 1971 | ry=range.y; 1972 | rz=range.z; 1973 | 1974 | rgvel.x=vel->x-obs_vel.x; 1975 | rgvel.y=vel->y-obs_vel.y; 1976 | rgvel.z=vel->z-obs_vel.z; 1977 | 1978 | Magnitude(&range); 1979 | 1980 | sin_lat=sin(geodetic->lat); 1981 | cos_lat=cos(geodetic->lat); 1982 | sin_theta=sin(geodetic->theta); 1983 | cos_theta=cos(geodetic->theta); 1984 | top_s=sin_lat*cos_theta*range.x+sin_lat*sin_theta*range.y-cos_lat*range.z; 1985 | top_e=-sin_theta*range.x+cos_theta*range.y; 1986 | top_z=cos_lat*cos_theta*range.x+cos_lat*sin_theta*range.y+sin_lat*range.z; 1987 | azim=atan(-top_e/top_s); /* Azimuth */ 1988 | 1989 | if (top_s>0.0) 1990 | { 1991 | azim=azim+pi; 1992 | } 1993 | 1994 | if (azim<0.0) 1995 | { 1996 | azim=azim+twopi; 1997 | } 1998 | 1999 | el=ArcSin(top_z/range.w); 2000 | obs_set->x=azim; /* Azimuth (radians) */ 2001 | obs_set->y=el; /* Elevation (radians) */ 2002 | obs_set->z=range.w; /* Range (kilometers) */ 2003 | 2004 | /* Range Rate (kilometers/second) */ 2005 | 2006 | obs_set->w=Dot(&range,&rgvel)/range.w; 2007 | 2008 | /* Corrections for atmospheric refraction */ 2009 | /* Reference: Astronomical Algorithms by Jean Meeus, pp. 101-104 */ 2010 | /* Correction is meaningless when apparent elevation is below horizon */ 2011 | 2012 | /*** The following adjustment for 2013 | atmospheric refraction is bypassed ***/ 2014 | 2015 | /* obs_set->y=obs_set->y+Radians((1.02/tan(Radians(Degrees(el)+10.3/(Degrees(el)+5.11))))/60); */ 2016 | 2017 | obs_set->y=el; 2018 | 2019 | /**** End bypass ****/ 2020 | 2021 | if (obs_set->y>=0.0) 2022 | { 2023 | SetFlag(VISIBLE_FLAG); 2024 | } 2025 | else 2026 | { 2027 | obs_set->y=el; /* Reset to true elevation */ 2028 | ClearFlag(VISIBLE_FLAG); 2029 | } 2030 | } 2031 | 2032 | void Calculate_RADec(double time, vector_t *pos, vector_t *vel, geodetic_t *geodetic, vector_t *obs_set) 2033 | { 2034 | /* Reference: Methods of Orbit Determination by */ 2035 | /* Pedro Ramon Escobal, pp. 401-402 */ 2036 | 2037 | double phi, theta, sin_theta, cos_theta, sin_phi, cos_phi, az, el, 2038 | Lxh, Lyh, Lzh, Sx, Ex, Zx, Sy, Ey, Zy, Sz, Ez, Zz, Lx, Ly, 2039 | Lz, cos_delta, sin_alpha, cos_alpha; 2040 | 2041 | Calculate_Obs(time,pos,vel,geodetic,obs_set); 2042 | 2043 | az=obs_set->x; 2044 | el=obs_set->y; 2045 | phi=geodetic->lat; 2046 | theta=FMod2p(ThetaG_JD(time)+geodetic->lon); 2047 | sin_theta=sin(theta); 2048 | cos_theta=cos(theta); 2049 | sin_phi=sin(phi); 2050 | cos_phi=cos(phi); 2051 | Lxh=-cos(az)*cos(el); 2052 | Lyh=sin(az)*cos(el); 2053 | Lzh=sin(el); 2054 | Sx=sin_phi*cos_theta; 2055 | Ex=-sin_theta; 2056 | Zx=cos_theta*cos_phi; 2057 | Sy=sin_phi*sin_theta; 2058 | Ey=cos_theta; 2059 | Zy=sin_theta*cos_phi; 2060 | Sz=-cos_phi; 2061 | Ez=0.0; 2062 | Zz=sin_phi; 2063 | Lx=Sx*Lxh+Ex*Lyh+Zx*Lzh; 2064 | Ly=Sy*Lxh+Ey*Lyh+Zy*Lzh; 2065 | Lz=Sz*Lxh+Ez*Lyh+Zz*Lzh; 2066 | obs_set->y=ArcSin(Lz); /* Declination (radians) */ 2067 | cos_delta=sqrt(1.0-Sqr(Lz)); 2068 | sin_alpha=Ly/cos_delta; 2069 | cos_alpha=Lx/cos_delta; 2070 | obs_set->x=AcTan(sin_alpha,cos_alpha); /* Right Ascension (radians) */ 2071 | obs_set->x=FMod2p(obs_set->x); 2072 | } 2073 | 2074 | /* .... SGP4/SDP4 functions end .... */ 2075 | 2076 | time_t CurrentTime() 2077 | { 2078 | return time(NULL); 2079 | } 2080 | 2081 | double FixAngle(double x) 2082 | { 2083 | /* This function reduces angles greater than 2084 | two pi by subtracting two pi from the angle */ 2085 | 2086 | while (x>twopi) 2087 | { 2088 | x-=twopi; 2089 | } 2090 | 2091 | return x; 2092 | } 2093 | 2094 | double PrimeAngle(double x) 2095 | { 2096 | /* This function is used in the FindMoon() function. */ 2097 | 2098 | x=x-360.0*floor(x/360.0); 2099 | return x; 2100 | } 2101 | 2102 | char *SubString(char *string, char start, char end) 2103 | { 2104 | /* This function returns a substring based on the starting 2105 | and ending positions provided. It is used heavily in 2106 | the AutoUpdate function when parsing 2-line element data. */ 2107 | 2108 | unsigned x, y; 2109 | 2110 | if (end>=start) 2111 | { 2112 | for (x=start, y=0; x<=end && string[x]!=0; x++) 2113 | { 2114 | if (string[x]!=' ') 2115 | { 2116 | temp[y]=string[x]; 2117 | y++; 2118 | } 2119 | } 2120 | 2121 | temp[y]=0; 2122 | return temp; 2123 | } 2124 | else 2125 | { 2126 | return NULL; 2127 | } 2128 | } 2129 | 2130 | void CopyString(char *source, char *destination, char start, char end) 2131 | { 2132 | /* This function copies elements of the string "source" 2133 | bounded by "start" and "end" into the string "destination". */ 2134 | 2135 | unsigned j, k=0; 2136 | 2137 | for (j=start; j<=end; j++) 2138 | { 2139 | if (source[k]!=0) 2140 | { 2141 | destination[j]=source[k]; 2142 | k++; 2143 | } 2144 | } 2145 | } 2146 | 2147 | char *Abbreviate(char *string, int n) 2148 | { 2149 | /* This function returns an abbreviated substring of the original, 2150 | including a '~' character if a non-blank character is chopped 2151 | out of the generated substring. n is the length of the desired 2152 | substring. It is used for abbreviating satellite names. */ 2153 | 2154 | strncpy(temp,string,79); 2155 | 2156 | if (temp[n]!=0 && temp[n]!=32) 2157 | { 2158 | temp[n-2]='~'; 2159 | temp[n-1]=temp[strlen(temp)-1]; 2160 | } 2161 | 2162 | temp[n]=0; 2163 | 2164 | return temp; 2165 | } 2166 | 2167 | char KepCheck(char *line1, char *line2) 2168 | { 2169 | /* This function scans line 1 and line 2 of a NASA 2-Line element 2170 | set and returns a 1 if the element set appears to be valid or 2171 | a 0 if it does not. If the data survives this torture test, 2172 | it's a pretty safe bet we're looking at a valid 2-line 2173 | element set and not just some random text that might pass 2174 | as orbital data based on a simple checksum calculation alone. */ 2175 | 2176 | int x; 2177 | unsigned sum1, sum2; 2178 | 2179 | /* Compute checksum for each line */ 2180 | 2181 | for (x=0, sum1=0, sum2=0; x<=67; sum1+=val[(int)line1[x]], sum2+=val[(int)line2[x]], x++); 2182 | 2183 | /* Perform a "torture test" on the data */ 2184 | 2185 | x=(val[(int)line1[68]]^(sum1%10)) | (val[(int)line2[68]]^(sum2%10)) | 2186 | (line1[0]^'1') | (line1[1]^' ') | (line1[7]^'U') | 2187 | (line1[8]^' ') | (line1[17]^' ') | (line1[23]^'.') | 2188 | (line1[32]^' ') | (line1[34]^'.') | (line1[43]^' ') | 2189 | (line1[52]^' ') | (line1[61]^' ') | (line1[62]^'0') | 2190 | (line1[63]^' ') | (line2[0]^'2') | (line2[1]^' ') | 2191 | (line2[7]^' ') | (line2[11]^'.') | (line2[16]^' ') | 2192 | (line2[20]^'.') | (line2[25]^' ') | (line2[33]^' ') | 2193 | (line2[37]^'.') | (line2[42]^' ') | (line2[46]^'.') | 2194 | (line2[51]^' ') | (line2[54]^'.') | (line1[2]^line2[2]) | 2195 | (line1[3]^line2[3]) | (line1[4]^line2[4]) | 2196 | (line1[5]^line2[5]) | (line1[6]^line2[6]) | 2197 | (isdigit(line1[68]) ? 0 : 1) | (isdigit(line2[68]) ? 0 : 1) | 2198 | (isdigit(line1[18]) ? 0 : 1) | (isdigit(line1[19]) ? 0 : 1) | 2199 | (isdigit(line2[31]) ? 0 : 1) | (isdigit(line2[32]) ? 0 : 1); 2200 | 2201 | return (x ? 0 : 1); 2202 | } 2203 | 2204 | void InternalUpdate(int x) 2205 | { 2206 | /* Updates data in TLE structure based on 2207 | line1 and line2 stored in structure. */ 2208 | 2209 | double tempnum; 2210 | 2211 | strncpy(sat.designator,SubString(sat.line1,9,16),8); 2212 | sat.designator[9]=0; 2213 | sat.catnum=atol(SubString(sat.line1,2,6)); 2214 | sat.year=atoi(SubString(sat.line1,18,19)); 2215 | sat.refepoch=atof(SubString(sat.line1,20,31)); 2216 | tempnum=1.0e-5*atof(SubString(sat.line1,44,49)); 2217 | sat.nddot6=tempnum/pow(10.0,(sat.line1[51]-'0')); 2218 | tempnum=1.0e-5*atof(SubString(sat.line1,53,58)); 2219 | sat.bstar=tempnum/pow(10.0,(sat.line1[60]-'0')); 2220 | sat.setnum=atol(SubString(sat.line1,64,67)); 2221 | sat.incl=atof(SubString(sat.line2,8,15)); 2222 | sat.raan=atof(SubString(sat.line2,17,24)); 2223 | sat.eccn=1.0e-07*atof(SubString(sat.line2,26,32)); 2224 | sat.argper=atof(SubString(sat.line2,34,41)); 2225 | sat.meanan=atof(SubString(sat.line2,43,50)); 2226 | sat.meanmo=atof(SubString(sat.line2,52,62)); 2227 | sat.drag=atof(SubString(sat.line1,33,42)); 2228 | sat.orbitnum=atof(SubString(sat.line2,63,67)); 2229 | } 2230 | 2231 | char *noradEvalue(double value) 2232 | { 2233 | /* Converts numeric values to E notation used in NORAD TLEs */ 2234 | 2235 | char string[15]; 2236 | 2237 | sprintf(string,"%11.4e",value*10.0); 2238 | 2239 | output[0]=string[0]; 2240 | output[1]=string[1]; 2241 | output[2]=string[3]; 2242 | output[3]=string[4]; 2243 | output[4]=string[5]; 2244 | output[5]=string[6]; 2245 | output[6]='-'; 2246 | output[7]=string[10]; 2247 | output[8]=0; 2248 | 2249 | return output; 2250 | } 2251 | 2252 | void Data2TLE(int x) 2253 | { 2254 | /* This function converts orbital data held in the numeric 2255 | portion of the sat tle structure to ASCII TLE format, 2256 | and places the result in ASCII portion of the structure. */ 2257 | 2258 | int i; 2259 | char string[15], line1[70], line2[70]; 2260 | unsigned sum; 2261 | 2262 | /* Fill lines with blanks */ 2263 | 2264 | for (i=0; i<70; line1[i]=32, line2[i]=32, i++); 2265 | 2266 | line1[69]=0; 2267 | line2[69]=0; 2268 | 2269 | /* Insert static characters */ 2270 | 2271 | line1[0]='1'; 2272 | line1[7]='U'; /* Unclassified */ 2273 | line2[0]='2'; 2274 | 2275 | line1[62]='0'; /* For publically released TLEs */ 2276 | 2277 | /* Insert orbital data */ 2278 | 2279 | sprintf(string,"%05ld",sat.catnum); 2280 | CopyString(string,line1,2,6); 2281 | CopyString(string,line2,2,6); 2282 | 2283 | CopyString(sat.designator,line1,9,16); 2284 | 2285 | sprintf(string,"%02d",sat.year); 2286 | CopyString(string,line1,18,19); 2287 | 2288 | sprintf(string,"%12.8f",sat.refepoch); 2289 | CopyString(string,line1,20,32); 2290 | 2291 | sprintf(string,"%.9f",fabs(sat.drag)); 2292 | 2293 | CopyString(string,line1,33,42); 2294 | 2295 | if (sat.drag<0.0) 2296 | { 2297 | line1[33]='-'; 2298 | } 2299 | else 2300 | { 2301 | line1[33]=32; 2302 | } 2303 | 2304 | CopyString(noradEvalue(sat.nddot6),line1,44,51); 2305 | CopyString(noradEvalue(sat.bstar),line1,53,60); 2306 | 2307 | sprintf(string,"%4lu",sat.setnum); 2308 | CopyString(string,line1,64,67); 2309 | 2310 | sprintf(string,"%9.4f",sat.incl); 2311 | CopyString(string,line2,7,15); 2312 | 2313 | sprintf(string,"%9.4f",sat.raan); 2314 | CopyString(string,line2,16,24); 2315 | 2316 | sprintf(string,"%13.12f",sat.eccn); 2317 | 2318 | /* Erase eccentricity's decimal point */ 2319 | 2320 | for (i=2; i<=9; string[i-2]=string[i], i++); 2321 | 2322 | CopyString(string,line2,26,32); 2323 | 2324 | sprintf(string,"%9.4f",sat.argper); 2325 | CopyString(string,line2,33,41); 2326 | 2327 | sprintf(string,"%9.5f",sat.meanan); 2328 | CopyString(string,line2,43,50); 2329 | 2330 | sprintf(string,"%12.9f",sat.meanmo); 2331 | CopyString(string,line2,52,62); 2332 | 2333 | sprintf(string,"%5lu",sat.orbitnum); 2334 | CopyString(string,line2,63,67); 2335 | 2336 | /* Compute and insert checksum for line 1 and line 2 */ 2337 | 2338 | for (i=0, sum=0; i<=67; sum+=val[(int)line1[i]], i++); 2339 | 2340 | line1[68]=(sum%10)+'0'; 2341 | 2342 | for (i=0, sum=0; i<=67; sum+=val[(int)line2[i]], i++); 2343 | 2344 | line2[68]=(sum%10)+'0'; 2345 | 2346 | line1[69]=0; 2347 | line2[69]=0; 2348 | 2349 | strcpy(sat.line1,line1); 2350 | strcpy(sat.line2,line2); 2351 | } 2352 | 2353 | double ReadBearing(char *input) 2354 | { 2355 | /* This function takes numeric input in the form of a character 2356 | string, and returns an equivalent bearing in degrees as a 2357 | decimal number (double). The input may either be expressed 2358 | in decimal format (74.2467) or degree, minute, second 2359 | format (74 14 48). This function also safely handles 2360 | extra spaces found either leading, trailing, or 2361 | embedded within the numbers expressed in the 2362 | input string. Decimal seconds are permitted. */ 2363 | 2364 | char string[20]; 2365 | double bearing=0.0, seconds; 2366 | int a, b, length, degrees, minutes; 2367 | 2368 | /* Copy "input" to "string", and ignore any extra 2369 | spaces that might be present in the process. */ 2370 | 2371 | string[0]=0; 2372 | length=strlen(input); 2373 | 2374 | for (a=0, b=0; a360.0 || bearing<-360.0) 2418 | { 2419 | bearing=0.0; 2420 | } 2421 | 2422 | return bearing; 2423 | } 2424 | 2425 | char ReadTLE(char *line0, char *line1, char *line2) 2426 | { 2427 | int la, lb, lc; 2428 | char error_flags,a,b,c,d; 2429 | 2430 | la = strnlen(line0,sizeof(sat.name)); 2431 | lb = strnlen(line1,sizeof(sat.line1)); 2432 | lc = strnlen(line2,sizeof(sat.line2)); 2433 | a = ((la == 0) || (la >= sizeof(sat.name))); 2434 | b = ((lb == 0) || (lb >= sizeof(sat.line1))); 2435 | c = ((lc == 0) || (lc >= sizeof(sat.line2))); 2436 | d = !KepCheck(line1, line2); 2437 | error_flags = (a << 3) | (b << 2) | (c << 1) | (d << 0); 2438 | 2439 | if (error_flags == 0) 2440 | { 2441 | strncpy(sat.name,line0,sizeof(sat.name)-1); 2442 | strncpy(sat.line1,line1,sizeof(sat.line1)-1); 2443 | strncpy(sat.line2,line2,sizeof(sat.line2)-1); 2444 | InternalUpdate(0); 2445 | } 2446 | 2447 | return error_flags; 2448 | } 2449 | 2450 | char ReadQTH(double lat, double lon, long alt) 2451 | { 2452 | //TODO: add sanity checks 2453 | qth.stnlat = lat; 2454 | qth.stnlong = lon; 2455 | qth.stnalt = alt; 2456 | 2457 | obs_geodetic.lat=qth.stnlat*deg2rad; 2458 | obs_geodetic.lon=-qth.stnlong*deg2rad; 2459 | obs_geodetic.alt=((double)qth.stnalt)/1000.0; 2460 | obs_geodetic.theta=0.0; 2461 | 2462 | return 0; 2463 | } 2464 | 2465 | 2466 | char ReadQTHFile() 2467 | { 2468 | FILE *fd; 2469 | 2470 | fd=fopen(qthfile,"r"); 2471 | if (fd!=NULL) 2472 | { 2473 | fgets(qth.callsign,16,fd); 2474 | qth.callsign[strlen(qth.callsign)-1]=0; 2475 | fscanf(fd,"%lf", &qth.stnlat); 2476 | fscanf(fd,"%lf", &qth.stnlong); 2477 | fscanf(fd,"%d", &qth.stnalt); 2478 | fclose(fd); 2479 | 2480 | obs_geodetic.lat=qth.stnlat*deg2rad; 2481 | obs_geodetic.lon=-qth.stnlong*deg2rad; 2482 | obs_geodetic.alt=((double)qth.stnalt)/1000.0; 2483 | obs_geodetic.theta=0.0; 2484 | return 0; 2485 | } 2486 | return -1; 2487 | } 2488 | 2489 | char CopyFile(char *source, char *destination) 2490 | { 2491 | /* This function copies file "source" to file "destination" 2492 | in 64k chunks. The permissions on the destination file 2493 | are set to rw-r--r-- (0644). A 0 is returned if no 2494 | errors are encountered. A 1 indicates a problem writing 2495 | to the destination file. A 2 indicates a problem reading 2496 | the source file. */ 2497 | 2498 | int x, sd, dd; 2499 | char error=0, buffer[65536]; 2500 | 2501 | sd=open(source,O_RDONLY); 2502 | 2503 | if (sd!=-1) 2504 | { 2505 | dd=open(destination,O_WRONLY | O_CREAT| O_TRUNC, 0644); 2506 | 2507 | if (dd!=-1) 2508 | { 2509 | x=read(sd,&buffer,65536); 2510 | 2511 | while (x) 2512 | { 2513 | write(dd,&buffer,x); 2514 | x=read(sd,&buffer,65536); 2515 | } 2516 | 2517 | close(dd); 2518 | } 2519 | else 2520 | { 2521 | error=1; 2522 | } 2523 | 2524 | close(sd); 2525 | } 2526 | else 2527 | { 2528 | error+=2; 2529 | } 2530 | 2531 | return error; 2532 | } 2533 | 2534 | void SaveQTH() 2535 | { 2536 | /* This function saves QTH data to the QTH data file. */ 2537 | 2538 | FILE *fd; 2539 | 2540 | fd=fopen(qthfile,"w"); 2541 | 2542 | fprintf(fd,"%s\n",qth.callsign); 2543 | fprintf(fd," %g\n",qth.stnlat); 2544 | fprintf(fd," %g\n",qth.stnlong); 2545 | fprintf(fd," %d\n",qth.stnalt); 2546 | 2547 | fclose(fd); 2548 | } 2549 | 2550 | void SaveTLE() 2551 | { 2552 | FILE *fd; 2553 | 2554 | /* Save orbital data to tlefile */ 2555 | 2556 | fd=fopen(tlefile,"w"); 2557 | 2558 | Data2TLE(0); 2559 | 2560 | /* Write name, line1, line2 to predict.tle */ 2561 | 2562 | fprintf(fd,"%s\n", sat.name); 2563 | fprintf(fd,"%s\n", sat.line1); 2564 | fprintf(fd,"%s\n", sat.line2); 2565 | 2566 | fclose(fd); 2567 | } 2568 | 2569 | long DayNum(int m, int d, int y) 2570 | { 2571 | /* This function calculates the day number from m/d/y. */ 2572 | 2573 | long dn; 2574 | double mm, yy; 2575 | 2576 | if (m<3) 2577 | { 2578 | y--; 2579 | m+=12; 2580 | } 2581 | 2582 | if (y<57) 2583 | { 2584 | y+=100; 2585 | } 2586 | 2587 | yy=(double)y; 2588 | mm=(double)m; 2589 | dn=(long)(floor(365.25*(yy-80.0))-floor(19.0+yy/100.0)+floor(4.75+yy/400.0)-16.0); 2590 | dn+=d+30*m+(long)floor(0.6*mm-0.3); 2591 | return dn; 2592 | } 2593 | 2594 | double CurrentDaynum() 2595 | { 2596 | /* Read the system clock and return the number 2597 | of days since 31Dec79 00:00:00 UTC (daynum 0) */ 2598 | int x; 2599 | struct timeval tptr; 2600 | double usecs, seconds; 2601 | 2602 | x=gettimeofday(&tptr,NULL); 2603 | 2604 | usecs=0.000001*(double)tptr.tv_usec; 2605 | seconds=usecs+(double)tptr.tv_sec; 2606 | 2607 | return ((seconds/86400.0)-3651.0); 2608 | } 2609 | 2610 | void FindMoon(double daynum) 2611 | { 2612 | /* This function determines the position of the moon, including 2613 | the azimuth and elevation headings, relative to the latitude 2614 | and longitude of the tracking station. This code was derived 2615 | from a Javascript implementation of the Meeus method for 2616 | determining the exact position of the Moon found at: 2617 | http://www.geocities.com/s_perona/ingles/poslun.htm. */ 2618 | 2619 | double jd, ss, t, t1, t2, t3, d, ff, l1, m, m1, ex, om, l, 2620 | b, w1, w2, bt, p, lm, h, ra, dec, z, ob, n, e, el, 2621 | az, teg, th, mm, dv; 2622 | 2623 | jd=daynum+2444238.5; 2624 | 2625 | t=(jd-2415020.0)/36525.0; 2626 | t2=t*t; 2627 | t3=t2*t; 2628 | l1=270.434164+481267.8831*t-0.001133*t2+0.0000019*t3; 2629 | m=358.475833+35999.0498*t-0.00015*t2-0.0000033*t3; 2630 | m1=296.104608+477198.8491*t+0.009192*t2+0.0000144*t3; 2631 | d=350.737486+445267.1142*t-0.001436*t2+0.0000019*t3; 2632 | ff=11.250889+483202.0251*t-0.003211*t2-0.0000003*t3; 2633 | om=259.183275-1934.142*t+0.002078*t2+0.0000022*t3; 2634 | om=om*deg2rad; 2635 | 2636 | /* Additive terms */ 2637 | 2638 | l1=l1+0.000233*sin((51.2+20.2*t)*deg2rad); 2639 | ss=0.003964*sin((346.56+132.87*t-0.0091731*t2)*deg2rad); 2640 | l1=l1+ss+0.001964*sin(om); 2641 | m=m-0.001778*sin((51.2+20.2*t)*deg2rad); 2642 | m1=m1+0.000817*sin((51.2+20.2*t)*deg2rad); 2643 | m1=m1+ss+0.002541*sin(om); 2644 | d=d+0.002011*sin((51.2+20.2*t)*deg2rad); 2645 | d=d+ss+0.001964*sin(om); 2646 | ff=ff+ss-0.024691*sin(om); 2647 | ff=ff-0.004328*sin(om+(275.05-2.3*t)*deg2rad); 2648 | ex=1.0-0.002495*t-0.00000752*t2; 2649 | om=om*deg2rad; 2650 | 2651 | l1=PrimeAngle(l1); 2652 | m=PrimeAngle(m); 2653 | m1=PrimeAngle(m1); 2654 | d=PrimeAngle(d); 2655 | ff=PrimeAngle(ff); 2656 | om=PrimeAngle(om); 2657 | 2658 | m=m*deg2rad; 2659 | m1=m1*deg2rad; 2660 | d=d*deg2rad; 2661 | ff=ff*deg2rad; 2662 | 2663 | /* Ecliptic Longitude */ 2664 | 2665 | l=l1+6.28875*sin(m1)+1.274018*sin(2.0*d-m1)+0.658309*sin(2.0*d); 2666 | l=l+0.213616*sin(2.0*m1)-ex*0.185596*sin(m)-0.114336*sin(2.0*ff); 2667 | l=l+0.058793*sin(2.0*d-2.0*m1)+ex*0.057212*sin(2.0*d-m-m1)+0.05332*sin(2.0*d+m1); 2668 | l=l+ex*0.045874*sin(2.0*d-m)+ex*0.041024*sin(m1-m)-0.034718*sin(d); 2669 | l=l-ex*0.030465*sin(m+m1)+0.015326*sin(2.0*d-2.0*ff)-0.012528*sin(2.0*ff+m1); 2670 | 2671 | l=l-0.01098*sin(2.0*ff-m1)+0.010674*sin(4.0*d-m1)+0.010034*sin(3.0*m1); 2672 | l=l+0.008548*sin(4.0*d-2.0*m1)-ex*0.00791*sin(m-m1+2.0*d)-ex*0.006783*sin(2.0*d+m); 2673 | 2674 | l=l+0.005162*sin(m1-d)+ex*0.005*sin(m+d)+ex*0.004049*sin(m1-m+2.0*d); 2675 | l=l+0.003996*sin(2.0*m1+2.0*d)+0.003862*sin(4.0*d)+0.003665*sin(2.0*d-3.0*m1); 2676 | 2677 | l=l+ex*0.002695*sin(2.0*m1-m)+0.002602*sin(m1-2.0*ff-2.0*d)+ex*0.002396*sin(2.0*d-m-2.0*m1); 2678 | 2679 | l=l-0.002349*sin(m1+d)+ex*ex*0.002249*sin(2.0*d-2.0*m)-ex*0.002125*sin(2.0*m1+m); 2680 | 2681 | l=l-ex*ex*0.002079*sin(2.0*m)+ex*ex*0.002059*sin(2.0*d-m1-2.0*m)-0.001773*sin(m1+2.0*d-2.0*ff); 2682 | 2683 | l=l+ex*0.00122*sin(4.0*d-m-m1)-0.00111*sin(2.0*m1+2.0*ff)+0.000892*sin(m1-3.0*d); 2684 | 2685 | l=l-ex*0.000811*sin(m+m1+2.0*d)+ex*0.000761*sin(4.0*d-m-2.0*m1)+ex*ex*.000717*sin(m1-2.0*m); 2686 | 2687 | l=l+ex*ex*0.000704*sin(m1-2.0*m-2.0*d)+ex*0.000693*sin(m-2.0*m1+2.0*d)+ex*0.000598*sin(2.0*d-m-2.0*ff)+0.00055*sin(m1+4.0*d); 2688 | 2689 | l=l+0.000538*sin(4.0*m1)+ex*0.000521*sin(4.0*d-m)+0.000486*sin(2.0*m1-d); 2690 | 2691 | l=l-0.001595*sin(2.0*ff+2.0*d); 2692 | 2693 | /* Ecliptic latitude */ 2694 | 2695 | b=5.128189*sin(ff)+0.280606*sin(m1+ff)+0.277693*sin(m1-ff)+0.173238*sin(2.0*d-ff); 2696 | b=b+0.055413*sin(2.0*d+ff-m1)+0.046272*sin(2.0*d-ff-m1)+0.032573*sin(2.0*d+ff); 2697 | 2698 | b=b+0.017198*sin(2.0*m1+ff)+9.266999e-03*sin(2.0*d+m1-ff)+0.008823*sin(2.0*m1-ff); 2699 | b=b+ex*0.008247*sin(2.0*d-m-ff)+0.004323*sin(2.0*d-ff-2.0*m1)+0.0042*sin(2.0*d+ff+m1); 2700 | 2701 | b=b+ex*0.003372*sin(ff-m-2.0*d)+ex*0.002472*sin(2.0*d+ff-m-m1)+ex*0.002222*sin(2.0*d+ff-m); 2702 | 2703 | b=b+0.002072*sin(2.0*d-ff-m-m1)+ex*0.001877*sin(ff-m+m1)+0.001828*sin(4.0*d-ff-m1); 2704 | 2705 | b=b-ex*0.001803*sin(ff+m)-0.00175*sin(3.0*ff)+ex*0.00157*sin(m1-m-ff)-0.001487*sin(ff+d)-ex*0.001481*sin(ff+m+m1)+ex*0.001417*sin(ff-m-m1)+ex*0.00135*sin(ff-m)+0.00133*sin(ff-d); 2706 | 2707 | b=b+0.001106*sin(ff+3.0*m1)+0.00102*sin(4.0*d-ff)+0.000833*sin(ff+4.0*d-m1); 2708 | 2709 | b=b+0.000781*sin(m1-3.0*ff)+0.00067*sin(ff+4.0*d-2.0*m1)+0.000606*sin(2.0*d-3.0*ff); 2710 | 2711 | b=b+0.000597*sin(2.0*d+2.0*m1-ff)+ex*0.000492*sin(2.0*d+m1-m-ff)+0.00045*sin(2.0*m1-ff-2.0*d); 2712 | 2713 | b=b+0.000439*sin(3.0*m1-ff)+0.000423*sin(ff+2.0*d+2.0*m1)+0.000422*sin(2.0*d-ff-3.0*m1); 2714 | 2715 | b=b-ex*0.000367*sin(m+ff+2.0*d-m1)-ex*0.000353*sin(m+ff+2.0*d)+0.000331*sin(ff+4.0*d); 2716 | 2717 | b=b+ex*0.000317*sin(2.0*d+ff-m+m1)+ex*ex*0.000306*sin(2.0*d-2.0*m-ff)-0.000283*sin(m1+3.0*ff); 2718 | 2719 | w1=0.0004664*cos(om*deg2rad); 2720 | w2=0.0000754*cos((om+275.05-2.3*t)*deg2rad); 2721 | bt=b*(1.0-w1-w2); 2722 | 2723 | /* Parallax calculations */ 2724 | 2725 | p=0.950724+0.051818*cos(m1)+0.009531*cos(2.0*d-m1)+0.007843*cos(2.0*d)+0.002824*cos(2.0*m1)+0.000857*cos(2.0*d+m1)+ex*0.000533*cos(2.0*d-m)+ex*0.000401*cos(2.0*d-m-m1); 2726 | 2727 | p=p+0.000173*cos(3.0*m1)+0.000167*cos(4.0*d-m1)-ex*0.000111*cos(m)+0.000103*cos(4.0*d-2.0*m1)-0.000084*cos(2.0*m1-2.0*d)-ex*0.000083*cos(2.0*d+m)+0.000079*cos(2.0*d+2.0*m1); 2728 | 2729 | p=p+0.000072*cos(4.0*d)+ex*0.000064*cos(2.0*d-m+m1)-ex*0.000063*cos(2.0*d+m-m1); 2730 | 2731 | p=p+ex*0.000041*cos(m+d)+ex*0.000035*cos(2.0*m1-m)-0.000033*cos(3.0*m1-2.0*d); 2732 | 2733 | p=p-0.00003*cos(m1+d)-0.000029*cos(2.0*ff-2.0*d)-ex*0.000029*cos(2.0*m1+m); 2734 | 2735 | p=p+ex*ex*0.000026*cos(2.0*d-2.0*m)-0.000023*cos(2.0*ff-2.0*d+m1)+ex*0.000019*cos(4.0*d-m-m1); 2736 | 2737 | b=bt*deg2rad; 2738 | lm=l*deg2rad; 2739 | moon_dx=3.0/(pi*p); 2740 | 2741 | /* Semi-diameter calculation */ 2742 | /* sem=10800.0*asin(0.272488*p*deg2rad)/pi; */ 2743 | 2744 | /* Convert ecliptic coordinates to equatorial coordinates */ 2745 | 2746 | z=(jd-2415020.5)/365.2422; 2747 | ob=23.452294-(0.46845*z+5.9e-07*z*z)/3600.0; 2748 | ob=ob*deg2rad; 2749 | dec=asin(sin(b)*cos(ob)+cos(b)*sin(ob)*sin(lm)); 2750 | ra=acos(cos(b)*cos(lm)/cos(dec)); 2751 | 2752 | if (lm>pi) 2753 | { 2754 | ra=twopi-ra; 2755 | } 2756 | 2757 | /* ra = right ascension */ 2758 | /* dec = declination */ 2759 | 2760 | n=qth.stnlat*deg2rad; /* North latitude of tracking station */ 2761 | e=-qth.stnlong*deg2rad; /* East longitude of tracking station */ 2762 | 2763 | /* Find siderial time in radians */ 2764 | 2765 | t=(jd-2451545.0)/36525.0; 2766 | teg=280.46061837+360.98564736629*(jd-2451545.0)+(0.000387933*t-t*t/38710000.0)*t; 2767 | 2768 | while (teg>360.0) 2769 | { 2770 | teg-=360.0; 2771 | } 2772 | 2773 | th=FixAngle((teg-qth.stnlong)*deg2rad); 2774 | h=th-ra; 2775 | 2776 | az=atan2(sin(h),cos(h)*sin(n)-tan(dec)*cos(n))+pi; 2777 | el=asin(sin(n)*sin(dec)+cos(n)*cos(dec)*cos(h)); 2778 | 2779 | moon_az=az/deg2rad; 2780 | moon_el=el/deg2rad; 2781 | 2782 | /* Radial velocity approximation. This code was derived 2783 | from "Amateur Radio Software", by John Morris, GM4ANB, 2784 | published by the RSGB in 1985. */ 2785 | 2786 | mm=FixAngle(1.319238+daynum*0.228027135); /* mean moon position */ 2787 | t2=0.10976; 2788 | t1=mm+t2*sin(mm); 2789 | dv=0.01255*moon_dx*moon_dx*sin(t1)*(1.0+t2*cos(mm)); 2790 | dv=dv*4449.0; 2791 | t1=6378.0; 2792 | t2=384401.0; 2793 | t3=t1*t2*(cos(dec)*cos(n)*sin(h)); 2794 | t3=t3/sqrt(t2*t2-t2*t1*sin(el)); 2795 | moon_dv=dv+t3*0.0753125; 2796 | 2797 | moon_dec=dec/deg2rad; 2798 | moon_ra=ra/deg2rad; 2799 | moon_gha=teg-moon_ra; 2800 | 2801 | if (moon_gha<0.0) 2802 | { 2803 | moon_gha+=360.0; 2804 | } 2805 | } 2806 | 2807 | void FindSun(double daynum) 2808 | { 2809 | /* This function finds the position of the Sun */ 2810 | 2811 | /* Zero vector for initializations */ 2812 | vector_t zero_vector={0,0,0,0}; 2813 | 2814 | /* Solar ECI position vector */ 2815 | vector_t solar_vector=zero_vector; 2816 | 2817 | /* Solar observed azi and ele vector */ 2818 | vector_t solar_set=zero_vector; 2819 | 2820 | /* Solar right ascension and declination vector */ 2821 | vector_t solar_rad=zero_vector; 2822 | 2823 | /* Solar lat, long, alt vector */ 2824 | geodetic_t solar_latlonalt; 2825 | 2826 | jul_utc=daynum+2444238.5; 2827 | 2828 | Calculate_Solar_Position(jul_utc, &solar_vector); 2829 | Calculate_Obs(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_set); 2830 | sun_azi=Degrees(solar_set.x); 2831 | sun_ele=Degrees(solar_set.y); 2832 | sun_range=1.0+((solar_set.z-AU)/AU); 2833 | sun_range_rate=1000.0*solar_set.w; 2834 | 2835 | Calculate_LatLonAlt(jul_utc, &solar_vector, &solar_latlonalt); 2836 | 2837 | sun_lat=Degrees(solar_latlonalt.lat); 2838 | sun_lon=360.0-Degrees(solar_latlonalt.lon); 2839 | 2840 | Calculate_RADec(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_rad); 2841 | 2842 | sun_ra=Degrees(solar_rad.x); 2843 | sun_dec=Degrees(solar_rad.y); 2844 | } 2845 | 2846 | void PreCalc(int x) 2847 | { 2848 | /* This function copies TLE data from PREDICT's sat structure 2849 | to the SGP4/SDP4's single dimensioned tle structure, and 2850 | prepares the tracking code for the update. */ 2851 | 2852 | strcpy(tle.sat_name,sat.name); 2853 | strcpy(tle.idesg,sat.designator); 2854 | tle.catnr=sat.catnum; 2855 | tle.epoch=(1000.0*(double)sat.year)+sat.refepoch; 2856 | tle.xndt2o=sat.drag; 2857 | tle.xndd6o=sat.nddot6; 2858 | tle.bstar=sat.bstar; 2859 | tle.xincl=sat.incl; 2860 | tle.xnodeo=sat.raan; 2861 | tle.eo=sat.eccn; 2862 | tle.omegao=sat.argper; 2863 | tle.xmo=sat.meanan; 2864 | tle.xno=sat.meanmo; 2865 | tle.revnum=sat.orbitnum; 2866 | 2867 | if (sat_db.squintflag) 2868 | { 2869 | calc_squint=1; 2870 | alat=deg2rad*sat_db.alat; 2871 | alon=deg2rad*sat_db.alon; 2872 | } 2873 | else 2874 | { 2875 | calc_squint=0; 2876 | } 2877 | 2878 | /* Clear all flags */ 2879 | 2880 | ClearFlag(ALL_FLAGS); 2881 | 2882 | /* Select ephemeris type. This function will set or clear the 2883 | DEEP_SPACE_EPHEM_FLAG depending on the TLE parameters of the 2884 | satellite. It will also pre-process tle members for the 2885 | ephemeris functions SGP4 or SDP4, so this function must 2886 | be called each time a new tle set is used. */ 2887 | 2888 | select_ephemeris(&tle); 2889 | } 2890 | 2891 | void Calc() 2892 | { 2893 | /* This is the stuff we need to do repetitively while tracking. */ 2894 | 2895 | /* Zero vector for initializations */ 2896 | vector_t zero_vector={0,0,0,0}; 2897 | 2898 | /* Satellite position and velocity vectors in ECI */ 2899 | vector_t vel=zero_vector; 2900 | vector_t pos=zero_vector; 2901 | 2902 | /* Observer position and velocity vectors in ECI */ 2903 | vector_t obs_pos=zero_vector; 2904 | vector_t obs_vel=zero_vector; 2905 | 2906 | /* Satellite Az, El, Range, Range rate */ 2907 | vector_t obs_set; 2908 | 2909 | /* Solar ECI position vector */ 2910 | vector_t solar_vector=zero_vector; 2911 | 2912 | /* Solar observed azi and ele vector */ 2913 | vector_t solar_set; 2914 | 2915 | /* Orbit normal vector used for calculating beta angle */ 2916 | vector_t orbit_n_vector=zero_vector; 2917 | 2918 | /* Satellite's predicted geodetic position */ 2919 | geodetic_t sat_geodetic; 2920 | 2921 | jul_utc=daynum+2444238.5; 2922 | 2923 | /* Convert satellite's epoch time to Julian */ 2924 | /* and calculate time since epoch in minutes */ 2925 | 2926 | jul_epoch=Julian_Date_of_Epoch(tle.epoch); 2927 | tsince=(jul_utc-jul_epoch)*xmnpda; 2928 | age=jul_utc-jul_epoch; 2929 | 2930 | /* Copy the ephemeris type in use to ephem string. */ 2931 | 2932 | if (isFlagSet(DEEP_SPACE_EPHEM_FLAG)) 2933 | { 2934 | strcpy(ephem,"SDP4"); 2935 | } 2936 | else 2937 | { 2938 | strcpy(ephem,"SGP4"); 2939 | } 2940 | 2941 | /* Call NORAD routines according to deep-space flag. */ 2942 | 2943 | if (isFlagSet(DEEP_SPACE_EPHEM_FLAG)) 2944 | { 2945 | SDP4(tsince, &tle, &pos, &vel); 2946 | } 2947 | else 2948 | { 2949 | SGP4(tsince, &tle, &pos, &vel); 2950 | } 2951 | 2952 | /* Scale position and velocity vectors to km and km/sec */ 2953 | 2954 | Convert_Sat_State(&pos, &vel); 2955 | 2956 | /* Calculate velocity of satellite */ 2957 | 2958 | Magnitude(&vel); 2959 | sat_vel=vel.w; 2960 | eci_x = pos.x; 2961 | eci_y = pos.y; 2962 | eci_z = pos.z; 2963 | eci_vx = vel.x; 2964 | eci_vy = vel.y; 2965 | eci_vz = vel.z; 2966 | 2967 | /** All angles in rads. Distance in km. Velocity in km/s **/ 2968 | /* Calculate satellite Azi, Ele, Range and Range-rate */ 2969 | 2970 | Calculate_Obs(jul_utc, &pos, &vel, &obs_geodetic, &obs_set); 2971 | 2972 | Calculate_User_PosVel(jul_utc, &obs_geodetic, &obs_pos, &obs_vel); 2973 | 2974 | eci_obs_x = obs_pos.x; 2975 | eci_obs_y = obs_pos.y; 2976 | eci_obs_z = obs_pos.z; 2977 | 2978 | /* Calculate satellite Lat North, Lon East and Alt. */ 2979 | 2980 | Calculate_LatLonAlt(jul_utc, &pos, &sat_geodetic); 2981 | 2982 | /* Calculate squint angle */ 2983 | 2984 | if (calc_squint) 2985 | { 2986 | squint=(acos(-(ax*rx+ay*ry+az*rz)/obs_set.z))/deg2rad; 2987 | } 2988 | 2989 | /* Calculate solar position and satellite eclipse depth. */ 2990 | /* Also set or clear the satellite eclipsed flag accordingly. */ 2991 | 2992 | Calculate_Solar_Position(jul_utc, &solar_vector); 2993 | 2994 | eci_sun_x = solar_vector.x; 2995 | eci_sun_y = solar_vector.y; 2996 | eci_sun_z = solar_vector.z; 2997 | 2998 | Cross(&pos,&vel,&orbit_n_vector); 2999 | 3000 | beta_angle = (pio2-Angle(&orbit_n_vector,&solar_vector))/deg2rad; 3001 | 3002 | Calculate_Obs(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_set); 3003 | 3004 | if (Sat_Eclipsed(&pos, &solar_vector, &eclipse_depth)) 3005 | { 3006 | SetFlag(SAT_ECLIPSED_FLAG); 3007 | } 3008 | else 3009 | { 3010 | ClearFlag(SAT_ECLIPSED_FLAG); 3011 | } 3012 | 3013 | if (isFlagSet(SAT_ECLIPSED_FLAG)) 3014 | { 3015 | sat_sun_status=0; /* Eclipse */ 3016 | } 3017 | else 3018 | { 3019 | sat_sun_status=1; /* In sunlight */ 3020 | } 3021 | 3022 | /* Convert satellite and solar data */ 3023 | sat_azi=Degrees(obs_set.x); 3024 | sat_ele=Degrees(obs_set.y); 3025 | sat_range=obs_set.z; 3026 | sat_range_rate=obs_set.w; 3027 | sat_lat=Degrees(sat_geodetic.lat); 3028 | sat_lon=Degrees(sat_geodetic.lon); 3029 | sat_alt=sat_geodetic.alt; 3030 | 3031 | fk=12756.33*acos(xkmper/(xkmper+sat_alt)); 3032 | fm=fk/1.609344; 3033 | 3034 | rv=(long)floor((tle.xno*xmnpda/twopi+age*tle.bstar*ae)*age+tle.xmo/twopi)+tle.revnum; 3035 | 3036 | sun_azi=Degrees(solar_set.x); 3037 | sun_ele=Degrees(solar_set.y); 3038 | 3039 | irk=(long)rint(sat_range); 3040 | isplat=(int)rint(sat_lat); 3041 | isplong=(int)rint(360.0-sat_lon); 3042 | iaz=(int)rint(sat_azi); 3043 | iel=(int)rint(sat_ele); 3044 | ma256=(int)rint(256.0*(phase/twopi)); 3045 | 3046 | if (sat_sun_status) 3047 | { 3048 | if (sun_ele<=-12.0 && rint(sat_ele)>=0.0) 3049 | { 3050 | findsun='+'; 3051 | } 3052 | else 3053 | { 3054 | findsun='*'; 3055 | } 3056 | } 3057 | else 3058 | { 3059 | findsun=' '; 3060 | } 3061 | } 3062 | 3063 | char AosHappens(int x) 3064 | { 3065 | /* This function returns a 1 if the satellite pointed to by 3066 | "x" can ever rise above the horizon of the ground station. */ 3067 | 3068 | double lin, sma, apogee; 3069 | 3070 | if (sat.meanmo==0.0) 3071 | { 3072 | return 0; 3073 | } 3074 | else 3075 | { 3076 | lin=sat.incl; 3077 | 3078 | if (lin>=90.0) 3079 | { 3080 | lin=180.0-lin; 3081 | } 3082 | 3083 | sma=331.25*exp(log(1440.0/sat.meanmo)*(2.0/3.0)); 3084 | apogee=sma*(1.0+sat.eccn)-xkmper; 3085 | 3086 | if ((acos(xkmper/(apogee+xkmper))+(lin*deg2rad)) > fabs(qth.stnlat*deg2rad)) 3087 | { 3088 | return 1; 3089 | } 3090 | else 3091 | { 3092 | return 0; 3093 | } 3094 | } 3095 | } 3096 | 3097 | char Decayed(int x, double time) 3098 | { 3099 | /* This function returns a 1 if it appears that the 3100 | satellite pointed to by 'x' has decayed at the 3101 | time of 'time'. If 'time' is 0.0, then the 3102 | current date/time is used. */ 3103 | 3104 | double satepoch; 3105 | 3106 | if (time==0.0) 3107 | { 3108 | time=CurrentDaynum(); 3109 | } 3110 | 3111 | satepoch=DayNum(1,0,sat.year)+sat.refepoch; 3112 | 3113 | if (satepoch+((16.666666-sat.meanmo)/(10.0*fabs(sat.drag))) < time) 3114 | { 3115 | return 1; 3116 | } 3117 | else 3118 | { 3119 | return 0; 3120 | } 3121 | } 3122 | 3123 | char Geostationary(int x) 3124 | { 3125 | /* This function returns a 1 if the satellite pointed 3126 | to by "x" appears to be in a geostationary orbit */ 3127 | 3128 | if (fabs(sat.meanmo-1.0027)<0.0002) 3129 | { 3130 | return 1; 3131 | } 3132 | else 3133 | { 3134 | return 0; 3135 | } 3136 | } 3137 | 3138 | double FindAOS() 3139 | { 3140 | /* This function finds and returns the time of AOS (aostime). */ 3141 | 3142 | int iterations = 0; 3143 | aostime=0.0; 3144 | 3145 | if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0) 3146 | { 3147 | Calc(); 3148 | 3149 | /* Get the satellite in range */ 3150 | 3151 | while (sat_ele<-1.0) 3152 | { 3153 | daynum-=0.00035*(sat_ele*((sat_alt/8400.0)+0.46)-2.0); 3154 | Calc(); 3155 | } 3156 | 3157 | /* Find AOS */ 3158 | 3159 | // TODO: FIX: We use a max on iterations here to avoid getting stuck when we no longer have an aos 3160 | while (aostime==0.0 && iterations < 100000) 3161 | { 3162 | if (fabs(sat_ele)<0.03) 3163 | { 3164 | aostime=daynum; 3165 | } 3166 | else 3167 | { 3168 | daynum-=sat_ele*sqrt(sat_alt)/530000.0; 3169 | Calc(); 3170 | } 3171 | iterations += 1; 3172 | } 3173 | } 3174 | 3175 | return aostime; 3176 | } 3177 | 3178 | double FindLOS() 3179 | { 3180 | lostime=0.0; 3181 | 3182 | if (Geostationary(indx)==0 && AosHappens(indx)==1 && Decayed(indx,daynum)==0) 3183 | { 3184 | Calc(); 3185 | 3186 | do 3187 | { 3188 | daynum+=sat_ele*sqrt(sat_alt)/502500.0; 3189 | Calc(); 3190 | 3191 | if (fabs(sat_ele) < 0.03) 3192 | { 3193 | lostime=daynum; 3194 | } 3195 | 3196 | } while (lostime==0.0); 3197 | } 3198 | 3199 | return lostime; 3200 | } 3201 | 3202 | double FindLOS2() 3203 | { 3204 | /* This function steps through the pass to find LOS. 3205 | FindLOS() is called to "fine tune" and return the result. */ 3206 | 3207 | do 3208 | { 3209 | daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/25000.0; 3210 | Calc(); 3211 | 3212 | } while (sat_ele>=0.0); 3213 | 3214 | return(FindLOS()); 3215 | } 3216 | 3217 | double NextAOS() 3218 | { 3219 | /* This function finds and returns the time of the next 3220 | AOS for a satellite that is currently in range. */ 3221 | 3222 | aostime=0.0; 3223 | 3224 | if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0) 3225 | { 3226 | daynum=FindLOS2()+0.014; /* Move to LOS + 20 minutes */ 3227 | } 3228 | 3229 | return (FindAOS()); 3230 | } 3231 | 3232 | // This function was extracted from SingleTrack and shows a number of derived parameters related 3233 | // to a satellite observed from a particular point on earth. 3234 | // 3235 | // NOTE: We don't support uplink, downlink, squint and related parameters. The related code 3236 | // is convoluted and it's never come up in our usage. FYI, the 'Edit Transponder Database' 3237 | // menu option is still marked "coming soon" :). We'll add it back if there's demand. 3238 | // 3239 | int MakeObservation(double obs_time, struct observation * obs) { 3240 | char geostationary=0, aoshappens=0, decayed=0, visibility=0, sunlit; 3241 | double doppler100=0.0, delay; 3242 | 3243 | PreCalc(0); 3244 | indx=0; 3245 | 3246 | if (sat_db.transponders>0) 3247 | { 3248 | PyErr_SetString(PredictException, "pypredict does not support transponder definition."); 3249 | return -1; 3250 | } 3251 | 3252 | daynum=obs_time; 3253 | aoshappens=AosHappens(indx); 3254 | geostationary=Geostationary(indx); 3255 | decayed=Decayed(indx,0.0); 3256 | 3257 | //Calcs 3258 | Calc(); 3259 | fk=12756.33*acos(xkmper/(xkmper+sat_alt)); 3260 | 3261 | if (sat_sun_status) 3262 | { 3263 | if (sun_ele<=-12.0 && sat_ele>=0.0) { 3264 | visibility='V'; 3265 | } else { 3266 | visibility='D'; 3267 | } 3268 | } else { 3269 | visibility='N'; 3270 | } 3271 | // gathering power seems much more useful than naked-eye visibility 3272 | sunlit = sat_sun_status; 3273 | 3274 | doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0); 3275 | delay=1000.0*((1000.0*sat_range)/299792458.0); 3276 | 3277 | //TODO: Seems like FindSun(daynum) should go in here 3278 | FindMoon(daynum); 3279 | 3280 | //printw(5+tshift,1,"Satellite Direction Velocity Footprint Altitude Slant Range"); 3281 | //printw(6+tshift,1,"--------- --------- -------- --------- -------- -----------"); 3282 | //printw(7+tshift,1," . Az mi mi mi mi"); 3283 | //printw(8+tshift,1," . El km km km km"); 3284 | //printw(16+bshift,1,"Eclipse Depth Orbital Phase Orbital Model Squint Angle AutoTracking"); 3285 | //printw(17+bshift,1,"------------- ------------- ------------- ------------ ------------"); 3286 | 3287 | obs->norad_id = sat.catnum; 3288 | strncpy(&(obs->name), &(sat.name), sizeof(obs->name)); 3289 | obs->epoch = (daynum+3651.0)*(86400.0); //See daynum=((start/86400.0)-3651.0); 3290 | obs->latitude = sat_lat; 3291 | obs->longitude = sat_lon; 3292 | obs->azimuth = sat_azi; 3293 | obs->elevation = sat_ele; 3294 | obs->orbital_velocity = 3600.0*sat_vel; 3295 | obs->footprint = fk; 3296 | obs->altitude = sat_alt; 3297 | obs->slant_range = sat_range; 3298 | obs->eclipse_depth = eclipse_depth/deg2rad; 3299 | obs->orbital_phase = 256.0*(phase/twopi); 3300 | strncpy(&(obs->orbital_model), &(ephem), sizeof(obs->orbital_model)); 3301 | obs->visibility = visibility; 3302 | obs->sunlit = sunlit; 3303 | obs->orbit = rv; 3304 | obs->geostationary = geostationary; 3305 | obs->has_aos = aoshappens; 3306 | obs->decayed = decayed; 3307 | obs->doppler = doppler100; 3308 | obs->eci_x = eci_x; 3309 | obs->eci_y = eci_y; 3310 | obs->eci_z = eci_z; 3311 | obs->eci_vx = eci_vx; 3312 | obs->eci_vy = eci_vy; 3313 | obs->eci_vz = eci_vz; 3314 | obs->eci_sun_x = eci_sun_x; 3315 | obs->eci_sun_y = eci_sun_y; 3316 | obs->eci_sun_z = eci_sun_z; 3317 | obs->eci_obs_x = eci_obs_x; 3318 | obs->eci_obs_y = eci_obs_y; 3319 | obs->eci_obs_z = eci_obs_z; 3320 | obs->beta_angle = beta_angle; 3321 | return 0; 3322 | } 3323 | 3324 | void PrintObservation(struct observation * obs) { 3325 | printf("NORAD_ID %d\n", obs->norad_id); 3326 | printf("Name %s\n", obs->name); 3327 | printf("Date(epoch) %f\n", obs->epoch); 3328 | printf("Latitude(N) %f\n", obs->latitude); 3329 | printf("Longitude(E) %f\n", obs->longitude); 3330 | printf("Azimuth %f\n", obs->azimuth); 3331 | printf("Elevation %f\n", obs->elevation); 3332 | printf("Velocity %f\n", obs->orbital_velocity); 3333 | printf("Footprint %f\n", obs->footprint); 3334 | printf("Altitude(km) %f\n", obs->altitude); 3335 | printf("Slant_Range(km) %f\n", obs->slant_range); 3336 | printf("Eclipse_Depth %f\n", obs->eclipse_depth); 3337 | printf("Orbital_Phase %f\n", obs->orbital_phase); 3338 | printf("Orbital_Model %s\n", obs->orbital_model); 3339 | printf("Visibility %c\n", obs->visibility); 3340 | printf("Sunlight %d\n", obs->sunlit); 3341 | printf("Orbit_Number %ld\n",obs->orbit); 3342 | printf("Geostationary %d\n", obs->geostationary); 3343 | printf("AOS_Happens %d\n", obs->has_aos); 3344 | printf("Decayed %d\n", obs->decayed); 3345 | printf("Doppler %f\n", obs->doppler); 3346 | printf("ECI_x %f\n", obs->eci_x); 3347 | printf("ECI_y %f\n", obs->eci_y); 3348 | printf("ECI_z %f\n", obs->eci_z); 3349 | printf("ECI_vx %f\n", obs->eci_vx); 3350 | printf("ECI_vy %f\n", obs->eci_vy); 3351 | printf("ECI_vz %f\n", obs->eci_vz); 3352 | printf("ECI_sun_x %f\n", obs->eci_sun_x); 3353 | printf("ECI_sun_y %f\n", obs->eci_sun_y); 3354 | printf("ECI_sun_z %f\n", obs->eci_sun_z); 3355 | printf("ECI_obs_x %f\n", obs->eci_obs_x); 3356 | printf("ECI_obs_y %f\n", obs->eci_obs_y); 3357 | printf("ECI_obs_z %f\n", obs->eci_obs_z); 3358 | printf("Beta_Angle(deg) %f\n", obs->beta_angle); 3359 | } 3360 | 3361 | PyObject * PythonifyObservation(observation * obs) { 3362 | //TODO: Add reference count? 3363 | return Py_BuildValue("{s:l,s:s,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:s,s:c,s:i,s:l,s:i,s:i,s:i,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d,s:d}", 3364 | "norad_id", obs->norad_id, 3365 | "name", obs->name, 3366 | "epoch", obs->epoch, 3367 | "latitude", obs->latitude, 3368 | "longitude", obs->longitude, 3369 | "azimuth", obs->azimuth, 3370 | "elevation", obs->elevation, 3371 | "orbital_velocity", obs->orbital_velocity, 3372 | "footprint", obs->footprint, 3373 | "altitude", obs->altitude, 3374 | "slant_range", obs->slant_range, 3375 | "eclipse_depth", obs->eclipse_depth, 3376 | "orbital_phase", obs->orbital_phase, 3377 | "orbital_model", obs->orbital_model, 3378 | "visibility", obs->visibility, 3379 | "sunlit", obs->sunlit, 3380 | "orbit", obs->orbit, 3381 | "geostationary", obs->geostationary, 3382 | "has_aos", obs->has_aos, 3383 | "decayed", obs->decayed, 3384 | "doppler", obs->doppler, 3385 | "eci_x", obs->eci_x, 3386 | "eci_y", obs->eci_y, 3387 | "eci_z", obs->eci_z, 3388 | "eci_vx", obs->eci_vx, 3389 | "eci_vy", obs->eci_vy, 3390 | "eci_vz", obs->eci_vz, 3391 | "eci_sun_x", obs->eci_sun_x, 3392 | "eci_sun_y", obs->eci_sun_y, 3393 | "eci_sun_z", obs->eci_sun_z, 3394 | "eci_obs_x", obs->eci_obs_x, 3395 | "eci_obs_y", obs->eci_obs_y, 3396 | "eci_obs_z", obs->eci_obs_z, 3397 | "beta_angle", obs->beta_angle 3398 | ); 3399 | } 3400 | 3401 | char load(PyObject *args) 3402 | { 3403 | //TODO: Not threadsafe, detect and raise warning? 3404 | int x; 3405 | char *env=NULL; 3406 | 3407 | /* Set up translation table for computing TLE checksums */ 3408 | for (x=0; x<=255; val[x]=0, x++); 3409 | for (x='0'; x<='9'; val[x]=x-'0', x++); 3410 | 3411 | val['-']=1; 3412 | 3413 | double epoch; 3414 | const char *tle0, *tle1, *tle2; 3415 | 3416 | if (!PyArg_ParseTuple(args, "(sss)|d(ddi)", 3417 | &tle0, &tle1, &tle2, &epoch, &qth.stnlat, &qth.stnlong, &qth.stnalt)) 3418 | { 3419 | // PyArg_ParseTuple will set appropriate exception string 3420 | return -1; 3421 | }; 3422 | 3423 | if (ReadTLE(tle0,tle1,tle2) != 0) 3424 | { 3425 | PyErr_SetString(PredictException, "Unable to process TLE"); 3426 | return -1; 3427 | } 3428 | 3429 | // If time isn't set, use current time. 3430 | if (PyObject_Length(args) < 2) 3431 | { 3432 | daynum=CurrentDaynum(); 3433 | } 3434 | else 3435 | { 3436 | daynum=((epoch/86400.0)-3651.0); 3437 | } 3438 | 3439 | // If we haven't already set groundstation location, use predict's default. 3440 | if (PyObject_Length(args) < 3) 3441 | { 3442 | FILE *fd; 3443 | env=getenv("HOME"); 3444 | sprintf(qthfile,"%s/.predict/predict.qth",env); 3445 | fd=fopen(qthfile,"r"); 3446 | if (fd!=NULL) 3447 | { 3448 | fgets(qth.callsign,16,fd); 3449 | qth.callsign[strlen(qth.callsign)-1]=0; 3450 | fscanf(fd,"%lf", &qth.stnlat); 3451 | fscanf(fd,"%lf", &qth.stnlong); 3452 | fscanf(fd,"%d", &qth.stnalt); 3453 | fclose(fd); 3454 | } else { 3455 | PyErr_SetString(PredictException, "QTH file could not be loaded."); 3456 | return -1; 3457 | } 3458 | } 3459 | obs_geodetic.lat=qth.stnlat*deg2rad; 3460 | obs_geodetic.lon=-qth.stnlong*deg2rad; 3461 | obs_geodetic.alt=((double)qth.stnalt)/1000.0; 3462 | obs_geodetic.theta=0.0; 3463 | 3464 | return 0; 3465 | } 3466 | 3467 | static PyObject* quick_find(PyObject* self, PyObject *args) 3468 | { 3469 | struct observation obs = { 0 }; 3470 | 3471 | if (load(args) != 0 || MakeObservation(daynum, &obs) != 0) 3472 | { 3473 | // load or MakeObservation will set appropriate exceptions if either fails. 3474 | return NULL; 3475 | } 3476 | 3477 | return PythonifyObservation(&obs); 3478 | } 3479 | 3480 | static char quick_find_docs[] = 3481 | "quick_find((tle_line0, tle_line1, tle_line2), time, (gs_lat, gs_lon, gs_alt))\n"; 3482 | 3483 | static PyObject* quick_predict(PyObject* self, PyObject *args) 3484 | { 3485 | double tle_epoch; 3486 | int lastel=0; 3487 | char errbuff[100]; 3488 | observation obs = { 0 }; 3489 | 3490 | PyObject* transit = PyList_New(0); 3491 | if (transit == NULL) 3492 | { 3493 | goto cleanup_and_raise_exception; 3494 | } 3495 | 3496 | if (load(args) != 0) 3497 | { 3498 | // load will set the appropriate exception string if it fails. 3499 | goto cleanup_and_raise_exception; 3500 | } 3501 | 3502 | tle_epoch=(double)DayNum(1,0,sat.year)+sat.refepoch; 3503 | if ((daynumtle_epoch+365.0)) 3504 | { 3505 | sprintf(errbuff, "day number %.0f too far from tle epoch day %.0f\n", daynum, tle_epoch); 3506 | PyErr_SetString(PredictException, errbuff); 3507 | goto cleanup_and_raise_exception; 3508 | } 3509 | 3510 | PreCalc(0); 3511 | Calc(); 3512 | if (MakeObservation(daynum, &obs) != 0) 3513 | { 3514 | // MakeObservation will set appropriate exception string 3515 | goto cleanup_and_raise_exception; 3516 | } 3517 | 3518 | if (!AosHappens(0)) 3519 | { 3520 | sprintf(errbuff, "%lu does not rise above horizon. No AOS.\n", sat.catnum); 3521 | PyErr_SetString(NoTransitException, errbuff); 3522 | goto cleanup_and_raise_exception; 3523 | } 3524 | 3525 | if (Geostationary(0)!=0) 3526 | { 3527 | sprintf(errbuff, "%lu is geostationary. Does not transit.\n", sat.catnum); 3528 | PyErr_SetString(PredictException, errbuff); 3529 | goto cleanup_and_raise_exception; 3530 | } 3531 | 3532 | if (Decayed(indx,daynum)!=0) 3533 | { 3534 | sprintf(errbuff, "%lu has decayed. Cannot calculate transit.\n", sat.catnum); 3535 | PyErr_SetString(PredictException, errbuff); 3536 | goto cleanup_and_raise_exception; 3537 | } 3538 | 3539 | /* Make Predictions */ 3540 | daynum=FindAOS(); 3541 | 3542 | if (daynum == 0) 3543 | { 3544 | sprintf(errbuff, "%lu no longer rises above horizon. No AOS.\n", sat.catnum); 3545 | PyErr_SetString(NoTransitException, errbuff); 3546 | goto cleanup_and_raise_exception; 3547 | } 3548 | 3549 | /* Construct the pass */ 3550 | PyObject * py_obs; 3551 | while (iel>=0) 3552 | { 3553 | if (MakeObservation(daynum, &obs) != 0) 3554 | { 3555 | //MakeObservation will set appropriate exception string 3556 | goto cleanup_and_raise_exception; 3557 | } 3558 | 3559 | py_obs = PythonifyObservation(&obs); 3560 | if (py_obs == NULL) { 3561 | //PythonifyObservation will set appropriate exception string 3562 | goto cleanup_and_raise_exception; 3563 | } 3564 | 3565 | if (PyList_Append(transit, py_obs) != 0) 3566 | { 3567 | Py_DECREF(py_obs); 3568 | goto cleanup_and_raise_exception; 3569 | } 3570 | Py_DECREF(py_obs); 3571 | lastel=iel; 3572 | daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/25000.0; 3573 | Calc(); 3574 | } 3575 | 3576 | if (lastel!=0) 3577 | { 3578 | daynum=FindLOS(); 3579 | //TODO: FindLOS can fail. Detect and log warning that transit end is approximate. 3580 | if (daynum > 0) { 3581 | Calc(); 3582 | 3583 | if (MakeObservation(daynum, &obs) != 0) 3584 | { 3585 | goto cleanup_and_raise_exception; 3586 | } 3587 | 3588 | py_obs = PythonifyObservation(&obs); 3589 | if (py_obs == NULL) { 3590 | goto cleanup_and_raise_exception; 3591 | } 3592 | 3593 | if (PyList_Append(transit, py_obs) != 0) 3594 | { 3595 | Py_DECREF(py_obs); 3596 | goto cleanup_and_raise_exception; 3597 | } 3598 | Py_DECREF(py_obs); 3599 | } 3600 | } 3601 | 3602 | return transit; 3603 | 3604 | cleanup_and_raise_exception: 3605 | Py_XDECREF(transit); 3606 | return NULL; 3607 | } 3608 | 3609 | static char quick_predict_docs[] = 3610 | "quick_predict((tle_line0, tle_line1, tle_line2), time, (gs_lat, gs_lon, gs_alt))\n"; 3611 | 3612 | static PyMethodDef pypredict_funcs[] = { 3613 | {"quick_find" , (PyCFunction)quick_find , METH_VARARGS, quick_find_docs}, 3614 | {"quick_predict", (PyCFunction)quick_predict, METH_VARARGS, quick_predict_docs}, 3615 | {NULL, NULL, 0, NULL} 3616 | }; 3617 | 3618 | #if PY_MAJOR_VERSION >= 3 3619 | static struct PyModuleDef moduledef = { 3620 | PyModuleDef_HEAD_INIT, 3621 | "cpredict", /* m_name */ 3622 | "Python port of the predict open source satellite tracking library", /* m_doc */ 3623 | -1, /* m_size */ 3624 | pypredict_funcs, /* m_methods */ 3625 | NULL, /* m_reload */ 3626 | NULL, /* m_traverse */ 3627 | NULL, /* m_clear */ 3628 | NULL, /* m_free */ 3629 | }; 3630 | #endif 3631 | 3632 | static PyObject * cpredictinit(void) 3633 | { 3634 | PyObject *m; 3635 | #if PY_MAJOR_VERSION >= 3 3636 | m = PyModule_Create(&moduledef); 3637 | #else 3638 | m = Py_InitModule3("cpredict", pypredict_funcs, 3639 | "Python port of the predict open source satellite tracking library"); 3640 | #endif 3641 | if (m == NULL) { 3642 | fprintf(stderr, "ERROR: Unable to initialize python module 'cpredict'\n"); 3643 | } 3644 | 3645 | // Add custom base exception for predict 3646 | PredictException = PyErr_NewException("cpredict.PredictException", NULL, NULL); 3647 | Py_INCREF(PredictException); 3648 | PyModule_AddObject(m, "PredictException", PredictException); 3649 | 3650 | // Specific exception for satellites that don't transit to distinguish from more serious failures 3651 | NoTransitException = PyErr_NewException("cpredict.NoTransitException", PredictException, NULL); 3652 | Py_INCREF(NoTransitException); 3653 | PyModule_AddObject(m, "NoTransitException", NoTransitException); 3654 | 3655 | return m; 3656 | } 3657 | 3658 | #if PY_MAJOR_VERSION >= 3 3659 | PyMODINIT_FUNC PyInit_cpredict(void) 3660 | { 3661 | return cpredictinit(); 3662 | } 3663 | #else 3664 | PyMODINIT_FUNC initcpredict(void) 3665 | { 3666 | cpredictinit(); 3667 | } 3668 | #endif 3669 | -------------------------------------------------------------------------------- /predict.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | 5 | from collections import namedtuple 6 | from copy import copy 7 | from cpredict import PredictException 8 | from cpredict import quick_find as _quick_find 9 | from cpredict import quick_predict as _quick_predict 10 | 11 | 12 | SolarWindow = namedtuple("SolarWindow", ["start", "end"]) 13 | 14 | 15 | def quick_find(tle, at, qth): 16 | tle = massage_tle(tle) 17 | qth = massage_qth(qth) 18 | return _quick_find(tle, at, qth) 19 | 20 | 21 | def quick_predict(tle, ts, qth): 22 | tle = massage_tle(tle) 23 | qth = massage_qth(qth) 24 | return _quick_predict(tle, ts, qth) 25 | 26 | 27 | STR_TYPE = str if sys.version_info.major > 2 else basestring # noqa: F821 28 | 29 | 30 | def host_qth(path="~/.predict/predict.qth"): 31 | path = os.path.abspath(os.path.expanduser(path)) 32 | try: 33 | with open(path) as qthfile: 34 | raw = [line.strip() for line in qthfile.readlines()] 35 | assert len(raw) == 4, "must match:\nname\nlat(N)\nlong(W)\nalt" % path 36 | return massage_qth(raw[1:]) 37 | except Exception as e: 38 | raise PredictException("Unable to process qth '%s' (%s)" % (path, e)) 39 | 40 | 41 | def massage_tle(tle): 42 | try: 43 | # TLE may or may not have been split into lines already 44 | if isinstance(tle, STR_TYPE): 45 | tle = tle.rstrip().split("\n") 46 | assert len(tle) == 3, "TLE must be 3 lines, not %d: %s" % (len(tle), tle) 47 | return tle 48 | # TODO: print a warning if TLE is 'too' old 49 | except Exception as e: 50 | raise PredictException(e) 51 | 52 | 53 | def massage_qth(qth): 54 | try: 55 | assert len(qth) == 3, ( 56 | "%s must consist of exactly three elements: (lat(N), long(W), alt(m))" % qth 57 | ) 58 | return (float(qth[0]), float(qth[1]), int(qth[2])) 59 | except ValueError as e: 60 | raise PredictException("Unable to convert '%s' (%s)" % (qth, e)) 61 | except Exception as e: 62 | raise PredictException(e) 63 | 64 | 65 | def observe(tle, qth, at=None): 66 | if at is None: 67 | at = time.time() 68 | return quick_find(tle, at, qth) 69 | 70 | 71 | def transits(tle, qth, ending_after=None, ending_before=None): 72 | if ending_after is None: 73 | ending_after = time.time() 74 | ts = ending_after 75 | while True: 76 | transit = quick_predict(tle, ts, qth) 77 | t = Transit( 78 | tle, 79 | qth, 80 | start=transit[0]["epoch"], 81 | end=transit[-1]["epoch"], 82 | _samples=transit, 83 | ) 84 | if ending_before is not None and t.end > ending_before: 85 | break 86 | if t.end > ending_after: 87 | yield t 88 | # Need to advance time cursor so predict doesn't yield same pass 89 | ts = t.end + 60 # seconds seems to be sufficient 90 | 91 | 92 | def active_transit(tle, qth, at=None): 93 | if at is None: 94 | at = time.time() 95 | transit = quick_predict(tle, at, qth) 96 | t = Transit( 97 | tle, qth, start=transit[0]["epoch"], end=transit[-1]["epoch"], _samples=transit 98 | ) 99 | return t if t.start <= at <= t.end else None 100 | 101 | 102 | class Transit: 103 | """A convenience class representing a pass of a satellite over a groundstation.""" 104 | 105 | def __init__(self, tle, qth, start, end, _samples=None): 106 | self.tle = tle 107 | self.qth = qth 108 | self.start = start 109 | self.end = end 110 | if _samples is None: 111 | self._samples = [] 112 | else: 113 | self._samples = [s for s in _samples if start <= s["epoch"] <= end] 114 | 115 | def peak(self, epsilon=0.1): 116 | """Return observation within epsilon seconds of maximum elevation. 117 | 118 | NOTE: Assumes elevation is strictly monotonic or concave over the [start,end] interval. 119 | """ 120 | ts = (self.end + self.start) / 2 121 | step = self.end - self.start 122 | while step > epsilon: 123 | step /= 4 124 | # Ascend the gradient at this step size 125 | direction = None 126 | while True: 127 | mid = observe(self.tle, self.qth, ts)["elevation"] 128 | left = observe(self.tle, self.qth, ts - step)["elevation"] 129 | right = observe(self.tle, self.qth, ts + step)["elevation"] 130 | # Break if we're at a peak 131 | if left <= mid >= right: 132 | break 133 | # Ascend up slope 134 | slope = -1 if (left > right) else 1 135 | # Break if we stepped over a peak (switched directions) 136 | if direction is None: 137 | direction = slope 138 | if direction != slope: 139 | break 140 | # Break if stepping would take us outside of transit 141 | next_ts = ts + (direction * step) 142 | if (next_ts < self.start) or (next_ts > self.end): 143 | break 144 | # Step towards the peak 145 | ts = next_ts 146 | return self.at(ts) 147 | 148 | def above(self, elevation, tolerance=0.001): 149 | """Return portion of transit that lies above argument elevation. 150 | 151 | Elevation at new endpoints will lie between elevation and elevation + tolerance unless 152 | endpoint of original transit is already above elevation, in which case it won't change, or 153 | entire transit is below elevation target, in which case resulting transit will have zero 154 | length. 155 | """ 156 | 157 | def capped_below(elevation, samples): 158 | """Quick heuristic to filter transits that can't reach target elevation. 159 | 160 | Assumes transit is unimodal and derivative is monotonic. i.e. transit is a smooth 161 | section of something that has ellipse-like geometry. 162 | """ 163 | limit = None 164 | 165 | if len(samples) < 3: 166 | raise ValueError("samples array must have length > 3") 167 | 168 | # Find samples that form a hump 169 | for i in range(len(samples) - 2): 170 | a, b, c = samples[i : i + 3] 171 | 172 | ae, be, ce = a["elevation"], b["elevation"], c["elevation"] 173 | at, bt, ct = a["epoch"], b["epoch"], c["epoch"] 174 | 175 | if ae < be > ce: 176 | left_step = bt - at 177 | right_step = ct - bt 178 | left_slope = (be - ae) / left_step 179 | right_slope = (be - ce) / right_step 180 | limit = be + max(left_step * right_slope, right_step * left_slope) 181 | break 182 | 183 | # If limit isn't set, we didn't find a hump, so max is at one of edges. 184 | if limit is None: 185 | limit = max(s["elevation"] for s in samples) 186 | 187 | return limit < elevation 188 | 189 | def add_sample(ts, samples): 190 | if ts not in [s["epoch"] for s in samples]: 191 | samples.append(self.at(ts)) 192 | samples.sort(key=lambda s: s["epoch"]) 193 | 194 | def interpolate(samples, elevation, tolerance): 195 | """Interpolate between adjacent samples straddling the elevation target.""" 196 | 197 | for i in range(len(samples) - 1): 198 | a, b = samples[i : i + 2] 199 | 200 | if any( 201 | abs(sample["elevation"] - elevation) <= tolerance 202 | for sample in [a, b] 203 | ): 204 | continue 205 | 206 | if (a["elevation"] < elevation) != (b["elevation"] < elevation): 207 | p = (elevation - a["elevation"]) / (b["elevation"] - a["elevation"]) 208 | t = a["epoch"] + p * (b["epoch"] - a["epoch"]) 209 | add_sample(t, samples) 210 | return True 211 | return False 212 | 213 | # math gets unreliable with a real small elevation tolerances (~1e-6), be safe. 214 | if tolerance < 0.0001: 215 | raise ValueError("Minimum tolerance of 0.0001") 216 | 217 | # Ensure we've got a well formed set of samples for iterative linear interpolation 218 | # We'll need at least three (2 needed for interpolating, 3 needed for filtering speedup) 219 | if self.start == self.end: 220 | return self 221 | samples = self._samples[:] 222 | add_sample(self.start, samples) 223 | add_sample(self.end, samples) 224 | if len(samples) < 3: 225 | add_sample((self.start + self.end) / 2, samples) 226 | 227 | # We need at least one sample point in the sample set above the desired elevation 228 | protrude = max(samples, key=lambda s: s["elevation"]) 229 | if protrude["elevation"] <= elevation: 230 | if not capped_below( 231 | elevation, samples 232 | ): # prevent expensive calculation on lost causes 233 | protrude = self.peak() 234 | add_sample( 235 | protrude["epoch"], samples 236 | ) # recalculation is wasteful, but this is rare 237 | 238 | if protrude["elevation"] <= elevation: 239 | start = protrude["epoch"] 240 | end = protrude["epoch"] 241 | else: 242 | # Aim for elevation + (tolerance / 2) +/- (tolerance / 2) to ensure we're >= elevation 243 | while interpolate( 244 | samples, elevation + float(tolerance) / 2, float(tolerance) / 2 245 | ): 246 | pass 247 | samples = [s for s in samples if s["elevation"] >= elevation] 248 | start = samples[0]["epoch"] 249 | end = samples[-1]["epoch"] 250 | return Transit(self.tle, self.qth, start, end, samples) 251 | 252 | def prune(self, fx, epsilon=0.1): 253 | """Return section of a transit where a pruning function is valid. 254 | 255 | Currently used to set elevation threshold, unclear what other uses it might have. fx must 256 | either return false everywhere or true for a contiguous period including the peak. 257 | """ 258 | 259 | peak = self.peak()["epoch"] 260 | if not fx(peak): 261 | start = peak 262 | end = peak 263 | else: 264 | if fx(self.start): 265 | start = self.start 266 | else: 267 | # Invariant is that fx(right) is True 268 | left, right = self.start, peak 269 | while (right - left) > epsilon: 270 | mid = (left + right) / 2 271 | if fx(mid): 272 | right = mid 273 | else: 274 | left = mid 275 | start = right 276 | if fx(self.end): 277 | end = self.end 278 | else: 279 | # Invariant is that fx(left) is True 280 | left, right = peak, self.end 281 | while (right - left) > epsilon: 282 | mid = (left + right) / 2 283 | if fx(mid): 284 | left = mid 285 | else: 286 | right = mid 287 | end = left 288 | # Use copy to allow subclassing of Transit object 289 | pruned = copy(self) 290 | pruned.start = start 291 | pruned.end = end 292 | return pruned 293 | 294 | def duration(self): 295 | return self.end - self.start 296 | 297 | def at(self, t, epsilon=0.001): 298 | if t < (self.start - epsilon) or t > (self.end + epsilon): 299 | raise PredictException( 300 | "time %f outside transit [%f, %f]" % (t, self.start, self.end) 301 | ) 302 | return observe(self.tle, self.qth, t) 303 | 304 | 305 | def find_solar_periods( 306 | start, 307 | end, 308 | tle, 309 | eclipse=False, 310 | eclipse_depth_threshold=0, 311 | large_predict_timestep=20, 312 | small_predict_timestep=1, 313 | ): 314 | """ 315 | Finds all sunlit (or eclipse, if eclipse is set) windows for a tle within a time range. 316 | """ 317 | qth = ( 318 | 0, 319 | 0, 320 | 0, 321 | ) # doesn't matter since we dont care about relative position from ground 322 | 323 | last_start = None 324 | ret = [] 325 | prev_period = -1 326 | t = start 327 | while t < end: 328 | obs = observe(tle, qth, t) 329 | if not eclipse: 330 | # Check for transitioning into sun, then transitioning out 331 | if prev_period == 0 and obs["sunlit"] == 1: 332 | last_start = t 333 | elif prev_period == 1 and obs["sunlit"] == 0: 334 | if last_start is not None: 335 | ret.append(SolarWindow(last_start, t)) 336 | last_start = None 337 | else: 338 | # Check for transitioning out of sun into eclipse 339 | if prev_period == 1 and obs["sunlit"] == 0: 340 | last_start = t 341 | elif prev_period == 0 and obs["sunlit"] == 1: 342 | if last_start is not None: 343 | ret.append(SolarWindow(last_start, t)) 344 | last_start = None 345 | prev_period = obs["sunlit"] 346 | # Use large steps if not near an eclipse boundary 347 | if abs(obs["eclipse_depth"]) > eclipse_depth_threshold: 348 | t += large_predict_timestep 349 | else: 350 | t += small_predict_timestep 351 | 352 | return ret 353 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, Extension 4 | 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setup( 9 | name="pypredict", 10 | version="1.7.2", 11 | author="Jesse Trutna", 12 | author_email="jesse@spire.com", 13 | maintainer="Spire Global Inc", 14 | maintainer_email="opensource@spire.com", 15 | description="Interface to the Predict satellite tracking and orbital prediction library", 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url="https://github.com/nsat/pypredict", 19 | py_modules=["predict"], 20 | ext_modules=[Extension("cpredict", ["predict.c"])], 21 | classifiers=[ 22 | "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", 23 | "Programming Language :: Python :: 2.7", 24 | "Programming Language :: Python :: 3.7", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /test_predict.py: -------------------------------------------------------------------------------- 1 | import predict 2 | import pytest 3 | 4 | from cpredict import PredictException 5 | 6 | TLE = ( 7 | "0 ISS (ZARYA)\n" 8 | "1 25544U 98067A 23096.18845088 .00017407 00000-0 31715-3 0 9991\n" 9 | "2 25544 51.6431 325.2993 0006862 159.1562 289.0392 15.49543408390622" 10 | ) 11 | QTH = (0.033889, 51.066389, 0.0) # Macapa, Brazil 12 | T1_IN_TRANSIT = 1680782200 # 2023-04-06T11:56:40Z 13 | T2_NOT_IN_TRANSIT = 1680783900 # 2023-04-06T12:25:00Z 14 | 15 | T1_WITHIN_A_YEAR = 1680782200 # 2023-04-06T11:56:40Z 16 | T2_AFTER_A_YEAR = 1713897435 # 2024-04-23T18:37:15Z 17 | 18 | 19 | def test_transits_are_truncated_if_the_overlap_the_start_or_end_times(): 20 | tle = predict.massage_tle(TLE) 21 | qth = predict.massage_qth(QTH) 22 | 23 | at = T1_IN_TRANSIT 24 | obs = predict.observe(tle, qth, at=at) 25 | assert obs["elevation"] > 0 26 | 27 | at = T2_NOT_IN_TRANSIT 28 | obs = predict.observe(tle, qth, at=at) 29 | assert obs["elevation"] < 0 30 | 31 | # should not raise a StopIteration 32 | next(predict.transits(tle, qth, ending_after=at)) 33 | 34 | 35 | def test_predict_of_tle_older_than_a_year(): 36 | # Test a TLE older than a year with a timestamp within a year of its epoch 37 | # Should not throw an exception 38 | predict.quick_predict(TLE, T1_WITHIN_A_YEAR, QTH) 39 | 40 | # Test a TLE older than a year with a timestamp outwith a year of its epoch 41 | # Should raise a PredictException 42 | with pytest.raises(PredictException) as e_info: 43 | predict.quick_predict(TLE, T2_AFTER_A_YEAR, QTH) 44 | -------------------------------------------------------------------------------- /test_predict_above.py: -------------------------------------------------------------------------------- 1 | import predict 2 | 3 | TLE = ( 4 | "0 ISS (ZARYA)\n" 5 | "1 25544U 98067A 23096.18845088 .00017407 00000-0 31715-3 0 9991\n" 6 | "2 25544 51.6431 325.2993 0006862 159.1562 289.0392 15.49543408390622" 7 | ) 8 | QTH = (0.033889, 51.066389, 0.0) # Macapa, Brazil 9 | 10 | ENDING_AFTER = 1680782200 # 2023-04-06T11:56:40Z 11 | ENDING_BEFORE = 1681387000 # 2023-04-13T11:56:40Z 12 | 13 | MIN_ELEVATION = 10 14 | MIN_DURATION = 1 15 | 16 | TRANSITS_REF = [ 17 | {"start": 1680781759.5005624, "end": 1680782160.6740706}, 18 | {"start": 1680823531.4431722, "end": 1680823906.632853}, 19 | {"start": 1680865311.7306323, "end": 1680865657.2993975}, 20 | {"start": 1680907105.8399603, "end": 1680907378.5976427}, 21 | {"start": 1680912921.9732332, "end": 1680913150.2145195}, 22 | {"start": 1680948922.5879023, "end": 1680949089.099553}, 23 | {"start": 1680954640.4831667, "end": 1680954960.0576594}, 24 | {"start": 1680996376.7773402, "end": 1680996737.0890093}, 25 | {"start": 1681038123.311641, "end": 1681038517.6482813}, 26 | {"start": 1681079878.0165846, "end": 1681080275.4995687}, 27 | {"start": 1681121639.6387215, "end": 1681122038.876846}, 28 | {"start": 1681163409.6062121, "end": 1681163779.7993734}, 29 | {"start": 1681205187.1739054, "end": 1681205524.298174}, 30 | {"start": 1681211103.2199135, "end": 1681211196.8242755}, 31 | {"start": 1681246981.065788, "end": 1681247238.721029}, 32 | {"start": 1681252780.5639017, "end": 1681253025.8338327}, 33 | {"start": 1681288803.5887175, "end": 1681288934.4458292}, 34 | {"start": 1681294498.2477252, "end": 1681294827.8092008}, 35 | {"start": 1681336233.208954, "end": 1681336599.2881413}, 36 | {"start": 1681377976.8515246, "end": 1681378373.9713566}, 37 | ] 38 | 39 | TOLERANCE = 0.1 40 | 41 | 42 | def test_transits_above(): 43 | tle = predict.massage_tle(TLE) 44 | qth = predict.massage_qth(QTH) 45 | 46 | transits = predict.transits( 47 | tle, qth, ending_after=ENDING_AFTER, ending_before=ENDING_BEFORE 48 | ) 49 | transits = [t.above(MIN_ELEVATION) for t in transits] 50 | transits = [t for t in transits if t.duration() > MIN_DURATION] 51 | transits = [{"start": t.start, "end": t.end} for t in transits] 52 | 53 | assert len(transits) == len(TRANSITS_REF) 54 | 55 | test_ok = True 56 | for i, t in enumerate(TRANSITS_REF): 57 | if ( 58 | transits[i]["start"] < t["start"] - TOLERANCE 59 | or t["start"] + TOLERANCE < transits[i]["start"] 60 | ): 61 | test_ok = False 62 | if ( 63 | transits[i]["end"] < t["end"] - TOLERANCE 64 | or t["end"] + TOLERANCE < transits[i]["end"] 65 | ): 66 | test_ok = False 67 | 68 | assert test_ok 69 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py38,py39,py310,py311 3 | 4 | [testenv] 5 | deps = pytest 6 | commands = 7 | make test 8 | allowlist_externals = make 9 | 10 | [gh-actions] 11 | python = 12 | 2.7: py27 13 | 3.7: py37 14 | 3.8: py38 15 | 3.9: py39 16 | 3.10: py310 17 | 3.11: py311 18 | --------------------------------------------------------------------------------