├── .github
└── workflows
│ ├── build.yml
│ └── docs.yml
├── .gitignore
├── LICENSE
├── README.md
├── db
├── README.md
├── bigBody.csv
├── feature.csv
├── satellite.csv
├── satelliteHistorical.csv
└── satelliteNames.csv
├── demo
├── Anton-Regular.ttf
├── horizonsys.png
├── orbitsDemo.nim
└── spkfiles.png
├── nim.cfg
├── orbits.nimble
├── src
├── orbits.nim
└── orbits
│ ├── astrometry.nim
│ ├── brightstars.json
│ ├── elements.json
│ ├── featuredb.nim
│ ├── horizon.nim
│ ├── simple.nim
│ ├── simulate.nim
│ ├── solarbodydb.nim
│ ├── solvers.nim
│ ├── spk.nim
│ ├── spkfiles.txt
│ ├── tle.nim
│ └── utils.nim
└── tests
├── all.nim
├── bigdipper.png
├── bigdipper_stars.png
├── brightstars3d.png
├── earthToMarsSim.nim
├── earthToMarsSim.png
├── generateOrbitalElements.nim
├── generateStats.nim
├── generatedOE.png
├── matching.png
├── merkuryPrecession.nim
├── merkuryPrecession.png
├── milkyway.png
├── nim.cfg
├── orbitsHorizon.nim
├── orbitsHorizon.png
├── orbitsSimple.nim
├── orbitsSimple.png
├── orbitsSimulated.nim
├── orbitsSimulated.png
├── orbitsSpk.nim
├── orbitsSpk.png
├── solarsystemSpk.nim
├── solvertest.nim
├── spacecraft.nim
├── spacecraft.png
├── starman.nim
├── starman.png
├── starman2.nim
├── test.c
└── test.nim
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Github Actions
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | strategy:
6 | fail-fast: false
7 | matrix:
8 | os: [ubuntu-latest, windows-latest]
9 |
10 | runs-on: ${{ matrix.os }}
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: jiro4989/setup-nim-action@v1
15 | with:
16 | repo-token: ${{ secrets.GITHUB_TOKEN }}
17 | - run: nimble test -y
18 | - run: nimble test --gc:orc -y
19 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: docs
2 | on:
3 | push:
4 | branches:
5 | - master
6 | env:
7 | nim-version: 'stable'
8 | nim-src: src/${{ github.event.repository.name }}.nim
9 | deploy-dir: .gh-pages
10 | jobs:
11 | docs:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: jiro4989/setup-nim-action@v1
16 | with:
17 | nim-version: ${{ env.nim-version }}
18 | - run: nimble install -Y
19 | - run: nimble doc --index:on --project --git.url:https://github.com/${{ github.repository }} --git.commit:master --out:${{ env.deploy-dir }} ${{ env.nim-src }}
20 | - name: "Copy to index.html"
21 | run: cp ${{ env.deploy-dir }}/${{ github.event.repository.name }}.html ${{ env.deploy-dir }}/index.html
22 | - name: Deploy documents
23 | uses: peaceiris/actions-gh-pages@v3
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | publish_dir: ${{ env.deploy-dir }}
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore files with no extention:
2 | *
3 | !*/
4 | !*.*
5 |
6 | # normal ignores:
7 | *.exe
8 | nimcache
9 | cache
10 | *.bsp
11 |
12 | # files to big!
13 | smallBody.csv
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT
2 |
3 | Copyright 2018 Andre von Houck
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Orbits - Orbital Mechanics Library for Nim.
2 |
3 | `nimble install orbits`
4 |
5 | 
6 |
7 | [API reference](https://treeform.github.io/orbits)
8 |
9 |
10 | Orbits are beautiful, with their gentle curves and sweeping arcs. They have captivated astronomers for thousands of years and have made mathematics and physics to what they are today. There is so much to the orbits if you dig dipper. Where do you get most accurate data? How do you compute transfer windows? Can you plot trajectories of spacecraft? This is what this library is about. An exploration of deep beyond with Nim at your side.
11 |
12 | # Simple Elliptical Orbits.
13 |
14 | Let's start out with some simple orbits that follow the simplest elliptical model. Here is how you can get position of the planets at any time and plot it:
15 |
16 |
17 |
18 | ```nim
19 | for planet in simpleElements:
20 | var step = planet.period / 360
21 | for i in 0..360:
22 | let pos = planet.posAt(step * float(i)) / AU
23 | ctx.lineTo(pos.x, pos.y)
24 | ctx.stroke()
25 | ```
26 |
27 | You do this using `orbital elements`. The minimum number of orbital elements you need are:
28 |
29 | * o: Longitude of the ascending node
30 | * i: Inclination
31 | * w: Argument of periaps
32 | * a: Semi-major axis, or mean distance from Sun
33 | * e: Eccentricity (0=circle, 0-1=ellipse, 1=parabola)
34 | * m: Mean anomaly (0 at perihelion increases uniformly with time)
35 | * n: Mean motion
36 |
37 | If you have exact time, you can turn `orbital elements` into `orbital vectors` which are
38 |
39 | * pos: x, y, z -- position
40 | * vel: x, y, z -- velocity
41 |
42 | Then you can accutally plot `orbital vectors`.
43 |
44 |
45 | # Kernel Files Orbits
46 |
47 | NASA uses [.bsp SPICE SP-kernels](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/info/intrdctn.html) files which accurately describe the orbits of planets with [chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials) after folks at JPL have collected all observation and ran them through supercomputer simulations. These orbits can be [downloaded](https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/) from NASA and are pretty accurate. Unfortunately they only exist from about 1000 AD to 3000 AD and only the larger bodies of solar system. Smaller bodies that don't have spice kernels need to use orbital elements to extarpolate their simpler elliptical orbits.
48 |
49 | You can find SPK files here: https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/
50 |
51 | ```nim
52 | var spkFile = readSpk("tests/de435.bsp")
53 | for planet in complexElements:
54 | var step = planet.period / 360
55 | for i in 0..360:
56 | let time = step * float(i)
57 | let pos = spkFile.posAt(time, planet.id, 0) / AU
58 | ctx.lineTo(pos.x, pos.y)
59 | ctx.stroke()
60 | ```
61 |
62 |
63 |
64 | Here you can see [Mercury precession](https://en.wikipedia.org/wiki/Tests_of_general_relativity#Perihelion_precession_of_Mercury). This is drawing 5 full orbit of merkury every 20 earth years. You can see the "wobble" of Merkury's orbit over 100 earth year time span.
65 |
66 |
67 |
68 |
69 | # JPL Horizon Orbits
70 |
71 | If getting data dumps from NASA is not enough you can also connect to NASA servers directly over [TELNET](https://en.wikipedia.org/wiki/Telnet) and query their databases in realtime. You can download a ton of interesting information from the [JPL Horizon System](https://ssd.jpl.nasa.gov/?horizons) you can’t get anywhere else. Thousands of positions of small bodies, asteroids, comets and select spacecraft throughout the solar system.
72 |
73 |
74 |
75 | ```nim
76 | var hz = newHorizonClient()
77 | for planet in simpleElements:
78 | let entries = hz.getOrbitalVectorsSeq(
79 | 0.0,
80 | planet.period,
81 | 360,
82 | planet.id, 0)
83 | for entry in entries:
84 | let pos = entry.pos / AU
85 | ctx.lineTo(pos.x, pos.y)
86 | ctx.closePath()
87 | ctx.stroke()
88 | hz.close()
89 | ```
90 |
91 | You can get positions of many things, Including spacecraft:
92 |
93 |
94 |
95 | Position of SpaceX's launched Starman riding a Elon's Cherry Tesla with NASA's accuracy.
96 |
97 |
98 |
99 |
100 | # Simulations
101 |
102 | You can also use this to simulate the solar system:
103 |
104 |
105 |
106 | # Astrometry
107 |
108 | You can also draw star fields and compute star positions:
109 |
110 |
111 |
112 | Given an image of a star field you can find the brightest stars and using the stars you can find the patch of sky you are looking at.
113 |
114 |
115 |
116 | Ever though about navigating by the stars?
117 |
118 |
119 |
120 | Finding names of stars from a pictures:
121 |
122 |
123 |
--------------------------------------------------------------------------------
/db/README.md:
--------------------------------------------------------------------------------
1 | # Body DB
2 |
3 | This folder contains different datbases for solar system objects.
4 |
5 | * bigBody.csv - major solar system bodies like planets and moons
6 | * smallBody.csv - astroids and commets.
7 | * feature.csv - surface feature names on moons and planets.
8 | * satellite.csv - satellite information from TLE db from NORAD.
--------------------------------------------------------------------------------
/db/bigBody.csv:
--------------------------------------------------------------------------------
1 | id,parentId,name,massKg,meanRadiusM,albedo,gm,equatorialRadiusM,densityKgM3,siderealRotationPeriodS,orbitRotationPeriodS,equatorialGravity,escapeVelocityM
2 | 0,0,Solar System Barycenter (SSB),1.989e+030,0.0,0.0,1.327178151e+020,0.0,0.0,0.0,0.0,0.0,0.0,
3 | 10,0,Sun,1.989e+030,695700000.0,0.0,1.327178151e+020,695700000.0,0.0,2164320.0,0.0,0.0,0.0,
4 | 1,1,Mercury Barycenter,3.30114e+023,0.0,0.0,22027153752600.0,0.0,0.0,0.0,7600543.819920001,0.0,0.0,
5 | 2,2,Venus Barycenter,4.86747e+024,0.0,0.0,324786316473000.0,0.0,0.0,0.0,19414149.052176,0.0,0.0,
6 | 3,3,Earth-Moot Barycenter,5.97237e+024,0.0,0.0,398511763382999.9,0.0,0.0,0.0,31558149.10224,0.0,0.0,
7 | 4,4,Mars Barycenter,6.417119999999999e+023,0.0,0.0,42818810740799.99,0.0,0.0,0.0,59355036.22176,0.0,0.0,
8 | 5,5,Jupiter Barycenter,1.898187e+027,0.0,0.0,1.266582359433e+017,0.0,0.0,0.0,374355659.124,0.0,0.0,
9 | 6,6,Saturn Barycenter,5.68336e+026,0.0,0.0,3.79227311024e+016,0.0,0.0,0.0,929292362.8848001,0.0,0.0,
10 | 7,7,Uranus Barycenter,8.68127e+025,0.0,0.0,5792655538930000.0,0.0,0.0,0.0,2651370019.3296,0.0,0.0,
11 | 8,8,Neptune Barycenter,1.024126e+026,0.0,0.0,6833572906339999.0,0.0,0.0,0.0,5200418560.032001,0.0,0.0,
12 | 9,9,Pluto-Charon Barycenter,1.303e+022,0.0,0.0,869438477000.0,0.0,0.0,0.0,7823780704.44,0.0,0.0,
13 | 199,1,Mercury,3.30114e+023,2439400.0,0.106,22027153752600.0,2440530.0,5429.1,5067031.680000001,7600543.819920001,3.7,4250.0,
14 | 299,2,Venus,4.86747e+024,6051800.0,0.65,324786316473000.0,6051800.0,5243.0,-20996755.2,19414149.052176,8.869999999999999,10360.0,
15 | 399,3,Earth,5.97237e+024,6371008.399999999,0.367,398511763382999.9,6378136.6,5513.6,86164.10035200001,31558149.10224,9.800000000000001,11190.0,
16 | 499,4,Mars,6.417119999999999e+023,3389500.0,0.15,42818810740799.99,3396190.0,3934.1,88642.664064,59355036.22176,3.71,5030.0,
17 | 599,5,Jupiter,1.898187e+027,69911000.0,0.52,1.266582359433e+017,71492000.0,1326.2,35729.856,374355659.124,24.79,60200.0,
18 | 699,6,Saturn,5.68336e+026,58232000.0,0.47,3.79227311024e+016,60268000.0,687.1,38362.464,929292362.8848001,10.44,36090.0,
19 | 799,7,Uranus,8.68127e+025,25362000.0,0.51,5792655538930000.0,25559000.0,1270.0,-62063.71200000001,2651370019.3296,8.869999999999999,21380.0,
20 | 899,8,Neptune,1.024126e+026,24622000.0,0.41,6833572906339999.0,24764000.0,1638.0,57995.99999999999,5200418560.032001,11.15,23560.0,
21 | 999,9,Pluto,1.303e+022,1188300.0,0.3,869438477000.0,1188300.0,1890.0,-551854.08,7823780704.44,0.62,1210.0,
22 | 301,3,Moon,7.347673092457352e+022,1737500.0,0.12,4902801000000.0,0.0,3344.0,0.0,0.0,0.0,0.0,
23 | 401,4,Phobos,1.065852989618724e+016,11100.0,0.07099999999999999,711200.0,0.0,1872.0,0.0,0.0,0.0,0.0,
24 | 402,4,Deimos,1476188406600735.0,6200.0,0.06800000000000001,98500.0,0.0,1471.0,0.0,0.0,0.0,0.0,
25 | 501,5,Io,8.931937973110891e+022,1821600.0,0.63,5959916000000.0,0.0,3528.0,0.0,0.0,0.0,0.0,
26 | 502,5,Europa,4.799843838749272e+022,1560800.0,0.67,3202739000000.0,0.0,3013.0,0.0,0.0,0.0,0.0,
27 | 503,5,Ganymede,1.481858468750515e+023,2631200.0,0.43,9887834000000.0,0.0,1942.0,0.0,0.0,0.0,0.0,
28 | 504,5,Callisto,1.075937379638192e+023,2410300.0,0.17,7179289000000.0,0.0,1834.0,0.0,0.0,0.0,0.0,
29 | 505,5,Amalthea,2.068162437674127e+018,83450.0,0.09,138000000.0,0.0,849.0,0.0,0.0,0.0,0.0,
30 | 506,5,Himalia,6.74400794893737e+018,85000.0,0.04,450000000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
31 | 507,5,Elara,8.692276911963721e+017,43000.0,0.04,58000000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
32 | 508,5,Pasiphae,2.997336866194387e+017,30000.0,0.04,20000000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
33 | 509,5,Sinope,7.493342165485966e+016,19000.0,0.04,5000000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
34 | 510,5,Lysithea,6.294407419008211e+016,18000.0,0.04,4200000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
35 | 511,5,Carme,1.31882822112553e+017,23000.0,0.04,8800000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
36 | 512,5,Ananke,2.997336866194386e+016,14000.0,0.04,2000000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
37 | 513,5,Leda,1.094027956160951e+016,10000.0,0.04,730000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
38 | 514,5,Thebe,1.498668433097193e+018,49300.0,0.047,100000000.0,0.0,3000.0,0.0,0.0,0.0,0.0,
39 | 515,5,Adrastea,7493342165485966.0,8200.0,0.1,500000.0,0.0,3000.0,0.0,0.0,0.0,0.0,
40 | 516,5,Metis,1.198934746477755e+017,21500.0,0.061,8000000.0,0.0,3000.0,0.0,0.0,0.0,0.0,
41 | 517,5,Callirrhoe,869227691196372.0,4300.0,0.04,58000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
42 | 518,5,Themisto,689387479224708.9,4000.0,0.04,46000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
43 | 519,5,Megaclite,209813580633607.1,2700.0,0.04,14000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
44 | 520,5,Taygete,164853527640691.3,2500.0,0.04,11000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
45 | 521,5,Chaldene,74933421654859.66,1900.0,0.04,5000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
46 | 522,5,Harpalyke,119893474647775.5,2200.0,0.04,8000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
47 | 523,5,Kalyke,194826896302635.1,2600.0,0.04,13000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
48 | 524,5,Iocaste,194826896302635.1,2600.0,0.04,13000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
49 | 525,5,Erinome,44960052992915.8,1600.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
50 | 526,5,Isonoe,74933421654859.66,1900.0,0.04,5000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
51 | 527,5,Praxidike,434613845598186.0,3400.0,0.04,29000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
52 | 528,5,Autonoe,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
53 | 529,5,Thyone,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
54 | 530,5,Hermippe,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
55 | 531,5,Aitne,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
56 | 532,5,Eurydome,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
57 | 533,5,Euanthe,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
58 | 534,5,Euporie,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
59 | 535,5,Orthosie,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
60 | 536,5,Sponde,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
61 | 537,5,Kale,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
62 | 538,5,Pasithee,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
63 | 539,5,Hegemone,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
64 | 540,5,Mneme,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
65 | 541,5,Aoede,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
66 | 542,5,Thelxinoe,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
67 | 543,5,Arche,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
68 | 544,5,Kallichore,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
69 | 545,5,Helike,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
70 | 546,5,Carpo,44960052992915.8,1500.0,0.04,3000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
71 | 547,5,Eukelade,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
72 | 548,5,Cyllene,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
73 | 549,5,Kore,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
74 | 550,5,Herse,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
75 | 551,5,S/2000 J11,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
76 | 552,5,S/2003 J2,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
77 | 553,5,S/2003 J3,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
78 | 554,5,S/2003 J4,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
79 | 555,5,S/2003 J5,89920105985831.59,2000.0,0.04,6000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
80 | 556,5,S/2003 J9,1498668433097.193,500.0,0.04,100.0,0.0,2600.0,0.0,0.0,0.0,0.0,
81 | 557,5,S/2003 J10,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
82 | 558,5,S/2003 J12,1498668433097.193,500.0,0.04,100.0,0.0,2600.0,0.0,0.0,0.0,0.0,
83 | 559,5,S/2003 J15,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
84 | 560,5,S/2003 J16,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
85 | 561,5,S/2003 J18,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
86 | 562,5,S/2003 J19,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
87 | 563,5,S/2003 J23,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
88 | 564,5,S/2010 J1,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
89 | 565,5,S/2010 J2,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
90 | 566,5,S/2011 J1,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
91 | 567,5,S/2011 J2,14986684330971.93,1000.0,0.04,1000.0,0.0,2600.0,0.0,0.0,0.0,0.0,
92 | 601,6,Mimas,3.750567620669036e+019,198200.0,0.962,2502600000.0,0.0,1150.0,0.0,0.0,0.0,0.0,
93 | 602,6,Enceladus,1.079445912306915e+020,252100.0,1.375,7202700000.0,0.0,1608.0,0.0,0.0,0.0,0.0,
94 | 603,6,Tethys,6.175518052210611e+020,533000.0,1.229,41206700000.0,0.0,973.0,0.0,0.0,0.0,0.0,
95 | 604,6,Dione,1.09574543018528e+021,561700.0,0.998,73114600000.0,0.0,1476.0,0.0,0.0,0.0,0.0,
96 | 605,6,Rhea,2.30708915128908e+021,764300.0,0.949,153942600000.0,0.0,1233.0,0.0,0.0,0.0,0.0,
97 | 606,6,Titan,1.345525230832405e+023,2574730.0,0.2,8978138200000.0,0.0,1882.0,0.0,0.0,0.0,0.0,
98 | 607,6,Hyperion,5.58553725015324e+018,135000.0,0.3,372700000.0,0.0,544.0,0.0,0.0,0.0,0.0,
99 | 608,6,Iapetus,1.805952411282576e+021,735600.0,0.6,120503800000.0,0.0,1083.0,0.0,0.0,0.0,0.0,
100 | 609,6,Phoebe,8.290633771893673e+018,106500.0,0.081,553200000.0,0.0,1638.0,0.0,0.0,0.0,0.0,
101 | 610,6,Janus,1.892818231001755e+018,89500.0,0.71,126300000.0,0.0,630.0,0.0,0.0,0.0,0.0,
102 | 611,6,Epimetheus,5.260326200171148e+017,58100.0,0.73,35100000.0,0.0,640.0,0.0,0.0,0.0,0.0,
103 | 612,6,Helene,1.138988009153867e+016,17600.0,1.67,760000.0,0.0,500.0,0.0,0.0,0.0,0.0,
104 | 613,6,Telesto,4046404769362422.0,12400.0,1.0,270000.0,0.0,500.0,0.0,0.0,0.0,0.0,
105 | 614,6,Calypso,2547736336265229.0,10700.0,1.34,170000.0,0.0,500.0,0.0,0.0,0.0,0.0,
106 | 615,6,Atlas,6594141105627650.0,15100.0,0.4,440000.0,0.0,460.0,0.0,0.0,0.0,0.0,
107 | 616,6,Prometheus,1.609569897146386e+017,43100.0,0.6,10740000.0,0.0,480.0,0.0,0.0,0.0,0.0,
108 | 617,6,Pandora,1.384769632181807e+017,40700.0,0.5,9240000.0,0.0,490.0,0.0,0.0,0.0,0.0,
109 | 618,6,Pan,4945605829220738.0,14100.0,0.5,330000.0,0.0,420.0,0.0,0.0,0.0,0.0,
110 | 619,6,Methone,8992010598583.16,1600.0,0.0,600.0,0.0,500.0,0.0,0.0,0.0,0.0,
111 | 620,6,Pallene,32970705528138.25,2500.0,0.0,2200.0,0.0,500.0,0.0,0.0,0.0,0.0,
112 | 621,6,Polydeuces,4496005299291.58,1300.0,0.0,300.0,0.0,500.0,0.0,0.0,0.0,0.0,
113 | 622,6,Daphnis,77930758521054.05,3800.0,0.0,5200.0,0.0,340.0,0.0,0.0,0.0,0.0,
114 | 623,6,Anthe,1498668433097.193,900.0,0.0,100.0,0.0,500.0,0.0,0.0,0.0,0.0,
115 | 624,6,Aegaeon,59946737323.88773,300.0,0.0,4.0,0.0,500.0,0.0,0.0,0.0,0.0,
116 | 625,6,Ymir,4945605829220738.0,9000.0,0.06,330000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
117 | 626,6,Paaliaq,8242676382034563.0,11000.0,0.06,550000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
118 | 627,6,Tarvos,2697603179574948.0,7500.0,0.06,180000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
119 | 628,6,Ijiraq,1198934746477755.0,6000.0,0.06,80000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
120 | 629,6,Suttungr,209813580633607.1,3500.0,0.06,14000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
121 | 630,6,Kiviuq,3297070552813825.0,8000.0,0.06,220000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
122 | 631,6,Mundilfari,209813580633607.1,3500.0,0.06,14000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
123 | 632,6,Albiorix,2.09813580633607e+016,16000.0,0.06,1400000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
124 | 633,6,Skathi,314720370950410.6,4000.0,0.06,21000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
125 | 634,6,Erriapus,764320900879568.5,5000.0,0.06,51000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
126 | 635,6,Siarnaq,3.896537926052702e+016,20000.0,0.06,2600000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
127 | 636,6,Thrymr,209813580633607.1,3500.0,0.06,14000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
128 | 637,6,Narvi,344693739612354.4,3500.0,0.04,23000.0,0.0,2300.0,0.0,0.0,0.0,0.0,
129 | 638,6,Aegir,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
130 | 639,6,Bebhionn,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
131 | 640,6,Bergelmir,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
132 | 641,6,Bestla,0.0,3500.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
133 | 642,6,Farbauti,0.0,2500.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
134 | 643,6,Fenrir,0.0,2000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
135 | 644,6,Fornjot,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
136 | 645,6,Hati,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
137 | 646,6,Hyrrokkin,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
138 | 647,6,Kari,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
139 | 648,6,Loge,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
140 | 649,6,Skoll,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
141 | 650,6,Surtur,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
142 | 651,6,Jarnsaxa,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
143 | 652,6,Greip,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
144 | 653,6,Tarqeq,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
145 | 654,6,S/2004 S7,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
146 | 655,6,S/2004 S12,0.0,2500.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
147 | 656,6,S/2004 S13,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
148 | 657,6,S/2004 S17,0.0,2000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
149 | 658,6,S/2006 S1,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
150 | 659,6,S/2006 S3,0.0,2500.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
151 | 660,6,S/2007 S2,0.0,3000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
152 | 661,6,S/2007 S3,0.0,2000.0,0.04,0.0,0.0,2300.0,0.0,0.0,0.0,0.0,
153 | 701,7,Ariel,1.294849526195975e+021,578900.0,0.39,86400000000.0,0.0,1592.0,0.0,0.0,0.0,0.0,
154 | 702,7,Umbriel,1.221414772974212e+021,584700.0,0.21,81500000000.0,0.0,1459.0,0.0,0.0,0.0,0.0,
155 | 703,7,Titania,3.419961364327795e+021,788900.0,0.27,228200000000.0,0.0,1662.0,0.0,0.0,0.0,0.0,
156 | 704,7,Oberon,2.883438065279e+021,761400.0,0.23,192400000000.0,0.0,1559.0,0.0,0.0,0.0,0.0,
157 | 705,7,Miranda,6.594141105627651e+019,235800.0,0.32,4400000000.0,0.0,1214.0,0.0,0.0,0.0,0.0,
158 | 706,7,Cordelia,4.49600529929158e+016,20100.0,0.07000000000000001,3000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
159 | 707,7,Ophelia,5.395206359149895e+016,21400.0,0.07000000000000001,3600000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
160 | 708,7,Bianca,9.291744285202598e+016,27000.0,0.065,6200000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
161 | 709,7,Cressida,3.431950711792572e+017,41000.0,0.06900000000000001,22900000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
162 | 710,7,Desdemona,1.78341543538566e+017,35000.0,0.08400000000000001,11900000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
163 | 711,7,Juliet,5.575046571121559e+017,53000.0,0.075,37200000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
164 | 712,7,Portia,1.681505981935051e+018,70000.0,0.06900000000000001,112200000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
165 | 713,7,Rosalind,2.547736336265229e+017,36000.0,0.072,17000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
166 | 714,7,Belinda,3.56683087077132e+017,45000.0,0.067,23800000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
167 | 715,7,Puck,2.89392874431068e+018,81000.0,0.104,193100000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
168 | 716,7,Caliban,2.997336866194387e+017,36000.0,0.04,20000000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
169 | 717,7,Sycorax,2.697603179574948e+018,75000.0,0.04,180000000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
170 | 718,7,Prospero,9.891211658441475e+016,25000.0,0.04,6600000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
171 | 719,7,Setebos,8.69227691196372e+016,24000.0,0.04,5800000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
172 | 720,7,Stephano,2.547736336265228e+016,16000.0,0.04,1700000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
173 | 721,7,Trinculo,4645872142601299.0,9000.0,0.04,310000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
174 | 722,7,Francisco,8392543225344282.0,11000.0,0.04,560000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
175 | 723,7,Margaret,6294407419008211.0,10000.0,0.04,420000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
176 | 724,7,Ferdinand,6294407419008211.0,10000.0,0.04,420000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
177 | 725,7,Perdita,1.798402119716632e+016,13000.0,0.07000000000000001,1200000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
178 | 726,7,Mab,8992010598583159.0,12000.0,0.103,600000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
179 | 727,7,Cupid,2997336866194387.0,9000.0,0.07000000000000001,200000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
180 | 801,8,Triton,2.139499055089553e+022,1353400.0,0.719,1427600000000.0,0.0,2059.0,0.0,0.0,0.0,0.0,
181 | 802,8,Nereid,3.087256972180218e+019,170000.0,0.155,2060000000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
182 | 803,8,Naiad,1.948268963026351e+017,33000.0,0.072,13000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
183 | 804,8,Thalassa,3.746671082742983e+017,41000.0,0.091,25000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
184 | 805,8,Despina,2.09813580633607e+018,75000.0,0.09,140000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
185 | 806,8,Galatea,3.746671082742983e+018,88000.0,0.079,250000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
186 | 807,8,Larissa,4.945605829220738e+018,97000.0,0.091,330000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
187 | 808,8,Proteus,5.03552593520657e+019,210000.0,0.096,3360000000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
188 | 809,8,Halimede,1.798402119716632e+017,31000.0,0.04,12000000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
189 | 810,8,Psamathe,4.945605829220738e+016,20000.0,0.04,3300000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
190 | 811,8,Sao,6.74400794893737e+016,22000.0,0.04,4500000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
191 | 812,8,Laomedeia,5.844806889079054e+016,21000.0,0.04,3900000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
192 | 813,8,Neso,1.648535276406913e+017,30000.0,0.04,11000000.0,0.0,1500.0,0.0,0.0,0.0,0.0,
193 | 814,8,S/2004 N1,4496005299291580.0,9000.0,0.1,300000.0,0.0,1300.0,0.0,0.0,0.0,0.0,
194 | 901,9,Charon,1.533137807058429e+021,603600.0,0.372,102300000000.0,0.0,1664.0,0.0,0.0,0.0,0.0,
195 | 902,9,Nix,1.948268963026351e+016,23000.0,0.35,1300000.0,0.0,2100.0,0.0,0.0,0.0,0.0,
196 | 903,9,Hydra,9.741344815131755e+016,30500.0,0.35,6500000.0,0.0,800.0,0.0,0.0,0.0,0.0,
197 | 904,9,Kerberos,1.648535276406913e+016,14000.0,0.35,1100000.0,0.0,1400.0,0.0,0.0,0.0,0.0,
198 | 905,9,Styx,0.0,10000.0,0.35,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
199 | 2000001,0,Ceres,9.385920609538426e+020,469700.0,0.09,62628400000.0,0.0,0.0,0.0,0.0,0.0,0.0,
200 | 2000002,0,Pallas,2.143095859328986e+020,272500.0,0.101,14300000000.0,0.0,0.0,0.0,0.0,0.0,0.0,
201 | 2000003,0,Juno,0.0,123298.0,0.214,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
202 | 2000004,0,Vesta,2.667629810913004e+020,262700.0,0.4228,17800000000.0,0.0,0.0,0.0,0.0,0.0,0.0,
203 | 2000005,0,Astraea,0.0,53349.5,0.274,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
204 | 2000006,0,Hebe,0.0,92590.0,0.2679,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
205 | 2000007,0,Iris,0.0,99915.0,0.2766,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
206 | 2000008,0,Flora,0.0,73745.5,0.226,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
207 | 2000009,0,Metis,0.0,95000.0,0.118,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
208 | 2000010,0,Hygiea,1.049067903168035e+020,203560.0,0.0717,7000000000.0,0.0,0.0,0.0,0.0,0.0,0.0,
209 | 2000011,0,Parthenope,0.0,71443.5,0.191,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
210 | 2000012,0,Victoria,0.0,57543.5,0.163,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
211 | 2000013,0,Egeria,0.0,101318.0,0.07000000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
212 | 2000014,0,Irene,0.0,76000.0,0.159,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
213 | 2000015,0,Eunomia,0.0,115844.5,0.248,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
214 | 2000016,0,Psyche,2.292962702638706e+019,113000.0,0.1203,1530000000.0,0.0,0.0,0.0,0.0,0.0,0.0,
215 | 2000017,0,Thetis,0.0,42449.5,0.193,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
216 | 2000018,0,Melpomene,0.0,69797.0,0.181,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
217 | 2000019,0,Fortuna,0.0,100000.0,0.037,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
218 | 2000020,0,Massalia,0.0,67840.0,0.241,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
219 | 2000021,0,Lutetia,0.0,47880.0,0.2212,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
220 | 2000022,0,Kalliope,7.358462006507219e+018,83768.0,0.166,491000000.0,0.0,0.0,0.0,0.0,0.0,0.0,
221 | 2000023,0,Thalia,0.0,53765.0,0.2536,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
222 | 2000024,0,Themis,0.0,99000.0,0.067,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
223 | 2000025,0,Phocaea,0.0,30527.0,0.35,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
224 | 2000026,0,Proserpina,0.0,47400.0,0.1966,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
225 | 2000027,0,Euterpe,0.0,48000.0,0.215,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
226 | 2000028,0,Bellona,0.0,60450.0,0.1763,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
227 | 2000029,0,Amphitrite,0.0,94779.5,0.216,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
228 | 2000030,0,Urania,0.0,46393.5,0.192,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
229 | 2000031,0,Euphrosyne,0.0,133540.0,0.053,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
230 | 2000032,0,Pomona,0.0,40380.0,0.2564,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
231 | 2000033,0,Polyhymnia,0.0,26464.5,0.24,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
232 | 2000034,0,Circe,0.0,66496.0,0.052,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
233 | 2000035,0,Leukothea,0.0,51527.5,0.066,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
234 | 2000036,0,Atalante,0.0,66421.0,0.036,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
235 | 2000037,0,Fides,0.0,54175.0,0.1826,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
236 | 2000038,0,Leda,0.0,46127.5,0.055,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
237 | 2000039,0,Laetitia,0.0,89742.0,0.269,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
238 | 2000040,0,Harmonia,0.0,55625.5,0.22,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
239 | 2000041,0,Daphne,0.0,102747.5,0.059,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
240 | 2000042,0,Isis,0.0,55498.5,0.139,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
241 | 2000043,0,Ariadne,0.0,35670.0,0.234,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
242 | 2000044,0,Nysa,0.0,35320.0,0.482,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
243 | 2000045,0,Eugenia,0.0,101163.5,0.045,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
244 | 2000046,0,Hestia,0.0,65735.5,0.046,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
245 | 2000047,0,Aglaja,0.0,84087.0,0.082,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
246 | 2000048,0,Doris,0.0,108236.5,0.065,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
247 | 2000049,0,Pales,0.0,83126.0,0.048,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
248 | 2000050,0,Virginia,0.0,42037.0,0.05,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
249 | 2000051,0,Nemausa,0.0,69079.5,0.105,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
250 | 2000052,0,Europa,0.0,151959.0,0.057,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
251 | 2000053,0,Kalypso,0.0,48631.0,0.031,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
252 | 2000054,0,Alexandra,0.0,80060.0,0.041,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
253 | 2000055,0,Pandora,0.0,42397.0,0.204,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
254 | 2000056,0,Melete,0.0,60666.5,0.057,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
255 | 2000057,0,Mnemosyne,0.0,56295.0,0.2149,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
256 | 2000058,0,Concordia,0.0,53258.5,0.044,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
257 | 2000059,0,Elpis,0.0,82559.5,0.044,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
258 | 2000060,0,Echo,0.0,21609.0,0.373,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
259 | 2000061,0,Danae,0.0,42968.5,0.203,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
260 | 2000062,0,Erato,0.0,53460.5,0.048,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
261 | 2000063,0,Ausonia,0.0,58022.0,0.125,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
262 | 2000064,0,Angelina,0.0,29146.0,0.483,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
263 | 2000065,0,Cybele,0.0,118630.0,0.0706,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
264 | 2000066,0,Maja,0.0,35910.0,0.0618,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
265 | 2000067,0,Asia,0.0,28154.5,0.228,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
266 | 2000068,0,Leto,0.0,61254.5,0.228,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
267 | 2000069,0,Hesperia,0.0,69065.0,0.1402,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
268 | 2000070,0,Panopaea,0.0,63955.5,0.038,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
269 | 2000071,0,Niobe,0.0,41710.0,0.3052,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
270 | 2000072,0,Feronia,0.0,37483.0,0.083,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
271 | 2000073,0,Klytia,0.0,22295.0,0.223,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
272 | 2000074,0,Galatea,0.0,59355.0,0.0431,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
273 | 2000075,0,Eurydike,0.0,31188.5,0.117,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
274 | 2000076,0,Freia,0.0,72711.5,0.058,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
275 | 2000077,0,Frigga,0.0,30695.0,0.177,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
276 | 2000078,0,Diana,0.0,60300.0,0.0706,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
277 | 2000079,0,Eurynome,0.0,31739.5,0.287,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
278 | 2000080,0,Sappho,0.0,34281.5,0.206,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
279 | 2000081,0,Terpsichore,0.0,58863.5,0.045,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
280 | 2000082,0,Alkmene,0.0,28810.5,0.167,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
281 | 2000083,0,Beatrix,0.0,55251.5,0.05,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
282 | 2000084,0,Klio,0.0,39580.0,0.0527,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
283 | 2000085,0,Io,0.0,77395.0,0.06660000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
284 | 2000086,0,Semele,0.0,54964.5,0.056,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
285 | 2000087,0,Sylvia,0.0,126525.5,0.046,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
286 | 2000088,0,Thisbe,0.0,116000.0,0.06710000000000001,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
287 | 2000089,0,Julia,0.0,72741.5,0.189,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
288 | 2000090,0,Antiope,0.0,57987.0,0.058,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
289 | 2000091,0,Aegina,0.0,51701.0,0.048,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
290 | 2000092,0,Undina,0.0,63210.0,0.2509,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
291 | 2000093,0,Minerva,0.0,77077.5,0.056,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
292 | 2000094,0,Aurora,0.0,102445.0,0.0395,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
293 | 2000095,0,Arethusa,0.0,73984.5,0.058,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
294 | 2000096,0,Aegle,0.0,88887.0,0.048,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
295 | 2000097,0,Klotho,0.0,50358.5,0.128,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
296 | 2000098,0,Ianthe,0.0,66394.0,0.029,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
297 | 2000099,0,Dike,0.0,33677.0,0.065,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
298 | 2000100,0,Hekate,0.0,42867.0,0.205,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
299 |
--------------------------------------------------------------------------------
/demo/Anton-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/demo/Anton-Regular.ttf
--------------------------------------------------------------------------------
/demo/horizonsys.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/demo/horizonsys.png
--------------------------------------------------------------------------------
/demo/orbitsDemo.nim:
--------------------------------------------------------------------------------
1 | import strformat, times
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import pixie/demo, vmath
4 |
5 | let now = epochTime()
6 |
7 | const
8 | planetScale = 0.25
9 |
10 | downloadSpk("de435.bsp")
11 |
12 | var
13 | spkFile = readSpk("de435.bsp")
14 | hz = newHorizonClient()
15 | slide = 0
16 | currentTime = 0.0
17 | keyWait = false
18 | scaleK = 10.0
19 |
20 | let
21 | spkfilesImage = readImage("demo/spkfiles.png")
22 | horizonsysImage = readImage("demo/horizonsys.png")
23 |
24 | hz.debug = false
25 |
26 | var font = readFont("demo/Anton-Regular.ttf")
27 | font.size = 20
28 | font.paint.color = color(1, 1, 1, 1).rgbx
29 |
30 | var font2 = readFont("demo/Anton-Regular.ttf")
31 | font2.size = 10
32 | font2.paint.color = color(1, 1, 1, 1).rgbx
33 |
34 | start("Orbits demo.")
35 | while true:
36 |
37 | scaleK += mouseWheelDelta * 0.2
38 |
39 | scaleK = clamp(scaleK, 1.0, 20.0)
40 |
41 | screen.fill(color(0.11, 0.14, 0.42, 1.0))
42 |
43 | case slide
44 | of 0:
45 | let text = "Totally fake Solar System."
46 | screen.fillText(font.typeset(text), vec2(10, 10))
47 |
48 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
49 |
50 | var path: Path
51 | path.circle(vec2(0, 0), 0.1)
52 | screen.fillPath(path, color(1,1,1,1), mat)
53 |
54 | for planet in simpleElements:
55 | var step = planet.period / 360
56 | var path: Path
57 |
58 | let dist = planet.posAt(0).length
59 |
60 | for i in 0..360:
61 | let th = float(i).toRadians()
62 | let pos = dvec3(sin(th), cos(th), 0) * dist / AU
63 | if i == 0:
64 | path.moveTo(pos.x.float32, pos.y.float32)
65 | else:
66 | path.lineTo(pos.x.float32, pos.y.float32)
67 | path.closePath()
68 |
69 | let th = (-currentTime*10 + planet.period)/ JULIAN_YEAR
70 | var currentPos = dvec3(sin(th), cos(th), 0) * dist / AU
71 | var pathBody: Path
72 | pathBody.circle(vec3(currentPos).xy, planetScale)
73 | screen.fillPath(pathBody, color(1,1,1,1), mat)
74 |
75 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 1.5)
76 |
77 | of 1:
78 | let text = "Solar System with Ecliptical Model."
79 | screen.fillText(font.typeset(text), vec2(10, 10))
80 |
81 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
82 |
83 | var path: Path
84 | path.circle(vec2(0, 0), 0.1)
85 | screen.fillPath(path, color(1,1,1,1), mat)
86 |
87 | for planet in simpleElements:
88 | var step = planet.period / 360
89 | var path: Path
90 |
91 | for i in 0..360:
92 | let time = step * float(i)
93 | let pos = planet.posAt(time) / AU
94 | if i == 0:
95 | path.moveTo(pos.x.float32, pos.y.float32)
96 | else:
97 | path.lineTo(pos.x.float32, pos.y.float32)
98 | path.closePath()
99 |
100 | var currentPos = planet.posAt(currentTime) / AU
101 | var pathBody: Path
102 | pathBody.circle(vec3(currentPos).xy, planetScale)
103 | screen.fillPath(pathBody, color(1,1,1,1), mat)
104 |
105 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 0.5)
106 |
107 | of 2:
108 | screen.draw(spkfilesImage)
109 |
110 | of 3:
111 | let text = "Solar System with NASA's Chebyshev polynomials."
112 | screen.fillText(font.typeset(text), vec2(10, 10))
113 |
114 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
115 |
116 | var path: Path
117 | path.circle(vec2(0, 0), 0.1)
118 | screen.fillPath(path, color(1,1,1,1), mat)
119 |
120 | for planet in simpleElements:
121 | var step = planet.period / 360
122 | var firstPos: DVec3
123 | var path: Path
124 |
125 | for i in 0..360:
126 | let time = step * float(i)
127 | let pos = spkFile.posAt(time, planet.id, 0) / AU
128 | if i == 0:
129 | path.moveTo(pos.x.float32, pos.y.float32)
130 | firstPos = pos
131 | else:
132 | path.lineTo(pos.x.float32, pos.y.float32)
133 | path.lineTo(firstPos.x.float32, firstPos.y.float32)
134 | path.closePath()
135 |
136 | var currentPos = spkFile.posAt(currentTime, planet.id, 0) / AU
137 | var pathBody: Path
138 | pathBody.circle(vec3(currentPos).xy, planetScale)
139 | screen.fillPath(pathBody, color(1,1,1,1), mat)
140 |
141 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 0.25)
142 |
143 | of 4:
144 | screen.draw(horizonsysImage, mat=scale(vec2(0.8)))
145 |
146 | of 5:
147 | let text = "Solar System with NASA's Horizon System."
148 | screen.fillText(font.typeset(text), vec2(10, 10))
149 |
150 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
151 |
152 | var path: Path
153 | path.circle(vec2(0, 0), 0.1)
154 | screen.fillPath(path, color(1,1,1,1), mat)
155 |
156 | proc drawOrbit(id: int, color: Color) =
157 |
158 | let elements = hz.getOrbitalElements(
159 | time = 0.0,
160 | targetId = id,
161 | observerId = 0
162 | )
163 |
164 | let period = elements.period
165 |
166 | var firstPos: DVec3
167 | var path: Path
168 |
169 | let entries = hz.getOrbitalVectorsSeq(
170 | fromTime = 0.0,
171 | toTime = period,
172 | steps = 360,
173 | targetId = id,
174 | observerId = 0)
175 | for i, entry in entries:
176 | let pos = entry.pos / AU
177 | if i == 0:
178 | path.moveTo(pos.x.float32, pos.y.float32)
179 | firstPos = pos
180 | else:
181 | path.lineTo(pos.x.float32, pos.y.float32)
182 | path.lineTo(firstPos.x.float32, firstPos.y.float32)
183 | path.closePath()
184 |
185 | var currentPos = entries.posAt(currentTime mod period) / AU
186 | var pathBody: Path
187 | pathBody.circle(vec3(currentPos).xy, planetScale)
188 | screen.fillPath(pathBody, color(1,1,1,1), mat)
189 |
190 | screen.strokePath(path, color, mat, strokeWidth = 0.1)
191 |
192 | for planet in simpleElements:
193 | drawOrbit(planet.id, color(1, 1, 1, 0.1))
194 |
195 | # for id in [2000001]:
196 | # drawOrbit(id, color(1, 0, 0, 0.1))
197 |
198 | block:
199 | #plot voyager1
200 | let id = -31
201 | let entries = hz.getOrbitalVectorsSeq(
202 | 242290800+DAY,
203 | 1552284021,
204 | 1000,
205 | id,
206 | 0)
207 | var path: Path
208 | for entry in entries:
209 | let pos = entry.pos / AU
210 | path.lineTo(pos.x.float32, pos.y.float32)
211 | screen.strokePath(path, color(1,1,1,1), mat, strokeWidth = 0.1)
212 | let pos = entries[^1].pos / AU
213 | screen.fillText(font2, " Voyager 1", mat * vec2(pos.x.float32, pos.y.float32))
214 |
215 | block:
216 | #plot voyager2
217 | let id = -32
218 | let entries = hz.getOrbitalVectorsSeq(
219 | 240908400+DAY,
220 | 1552284021,
221 | 1000,
222 | id,
223 | 0)
224 | var path: Path
225 | for entry in entries:
226 | let pos = entry.pos / AU
227 | path.lineTo(pos.x.float32, pos.y.float32)
228 | screen.strokePath(path, color(1,1,1,1), mat, strokeWidth = 0.1)
229 | let pos = entries[^1].pos / AU
230 | screen.fillText(font2, " Voyager 2", mat * vec2(pos.x.float32, pos.y.float32))
231 |
232 | block:
233 | #plot voyager2
234 | let id = -98
235 | let entries = hz.getOrbitalVectorsSeq(
236 | 1137657600+DAY,
237 | 1552284021,
238 | 1000,
239 | id,
240 | 0)
241 | var path: Path
242 | for entry in entries:
243 | let pos = entry.pos / AU
244 | path.lineTo(pos.x.float32, pos.y.float32)
245 | screen.strokePath(path, color(1,1,1,1), mat, strokeWidth = 0.1)
246 | let pos = entries[^1].pos / AU
247 | screen.fillText(font2, " New Horizon", mat * vec2(pos.x.float32, pos.y.float32))
248 |
249 |
250 | # for id in [136199]:
251 | # drawOrbit(id, color(1, 0, 0, 0.9))
252 |
253 | else:
254 | discard
255 |
256 | if isKeyDown(KEY_RIGHT) or isKeyDown(KEY_SPACE):
257 | if not keyWait:
258 | keyWait = true
259 | slide = min(slide.ord + 1, 5)
260 | elif isKeyDown(KEY_LEFT):
261 | if not keyWait:
262 | keyWait = true
263 | slide = max(slide.ord - 1, 0)
264 | else:
265 | keyWait = false
266 |
267 | tick()
268 |
269 | currentTime += 60*60*25 * 1 # day per frame
270 |
--------------------------------------------------------------------------------
/demo/spkfiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/demo/spkfiles.png
--------------------------------------------------------------------------------
/nim.cfg:
--------------------------------------------------------------------------------
1 | --define:ssl
2 |
--------------------------------------------------------------------------------
/orbits.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "0.1.0"
4 | author = "Andre von Houck"
5 | description = "Orbits - orbital mechanics library for nim."
6 | license = "MIT"
7 | srcDir = "src"
8 |
9 | # Dependencies
10 |
11 | requires "nim >= 1.0.0"
12 | requires "chroma >= 0.2.5"
13 | requires "pixie >= 4.3.0"
14 | requires "vmath >= 1.1.4"
15 | requires "print >= 1.0.2"
16 | requires "cairo >= 1.1.0"
17 | requires "puppy >= 2.0.3"
18 |
--------------------------------------------------------------------------------
/src/orbits.nim:
--------------------------------------------------------------------------------
1 | import orbits/simple
2 | import orbits/spk
3 | import orbits/horizon
4 |
5 | export simple
6 | export spk
7 | export horizon
--------------------------------------------------------------------------------
/src/orbits/astrometry.nim:
--------------------------------------------------------------------------------
1 | import flippy, vmath, random, print, chroma, json, strutils
2 | import cairo
3 |
4 |
5 | type Star = object
6 | id: int
7 | name: string
8 | otherName: string
9 | th: float
10 | ph: float
11 | luma: float
12 | pos: Vec3
13 |
14 | type StarTri = object
15 | aID, bID, cID: int
16 | ab, bc, ca: float
17 |
18 | var brightStars: seq[Star]
19 | var starTris: seq[StarTri]
20 |
21 | proc toRad(v: float): float =
22 | v / 180 * PI
23 |
24 |
25 | proc readBrightStars() =
26 | let brightStarsJson = parseJson(readFile("src/orbits/brightstars.json"))
27 | var id = 0
28 | for star in brightStarsJson:
29 | var star = Star(
30 | name: star[0].getStr().replace("\xC3\x8E\xC2", "\xCE"),
31 | otherName: star[1].getStr(),
32 | ph: star[2].getFloat(),
33 | th: star[3].getFloat() + 90,
34 | luma: star[4].getFloat()
35 | )
36 | #if "U" notin star.name: continue
37 | #if star.luma > 5: continue
38 | star.id = id
39 | star.pos = -vec3(
40 | sin(star.th.toRad) * cos(star.ph.toRad),
41 | sin(star.th.toRad) * sin(star.ph.toRad),
42 | cos(star.th.toRad)
43 | )
44 | brightStars.add star
45 | inc id
46 |
47 |
48 |
49 | proc normalize(tri: var StarTri) =
50 | let maxDist = max([tri.ab, tri.bc, tri.ca])
51 | tri.ab /= maxDist
52 | tri.bc /= maxDist
53 | tri.ca /= maxDist
54 | # sort tri
55 | if tri.ca > tri.bc:
56 | (tri.bId, tri.cId) = (tri.cId, tri.bId)
57 | (tri.bc, tri.ca) = (tri.ca, tri.bc)
58 | if tri.bc > tri.ab:
59 | (tri.aId, tri.bId) = (tri.bId, tri.aId)
60 | (tri.ab, tri.bc) = (tri.bc, tri.ab)
61 |
62 |
63 | proc genStarTris() =
64 | const minDist = 0.2
65 | for a in brightStars:
66 | for b in brightStars:
67 | if a.id != b.id:
68 | var ab = dist(a.pos, b.pos)
69 | if ab < minDist:
70 | #print "star pair", a.id, b.id, ab
71 | for c in brightStars:
72 | if a.id != c.id and b.id != c.id:
73 | var bc = dist(b.pos, c.pos)
74 | if bc < minDist:
75 | #print " star tri", a.id, b.id, c.id, ab, bc
76 | var ca = dist(c.pos, a.pos)
77 | var tri = StarTri(
78 | aID: a.id, bID: b.id, cID: c.id,
79 | ab: ab, bc: bc, ca: ca
80 | )
81 | tri.normalize()
82 | if brightStars[tri.aID].otherName == "Dubhe" and
83 | brightStars[tri.bID].otherName == "Megrez" and
84 | brightStars[tri.cID].otherName == "Merak":
85 | starTris.add tri
86 |
87 | print "genreated: ", starTris.len
88 |
89 | proc drawMilkyWay() =
90 | var
91 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 500)
92 | ctx = surface.create()
93 | ctx.selectFontFace("Sans", FONT_SLANT_NORMAL, FONT_WEIGHT_NORMAL)
94 | ctx.setFontSize(12.0)
95 |
96 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1)
97 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
98 | ctx.fill()
99 |
100 | for star in brightStars:
101 | var s = 2/(star.luma + 1.44 + 1)
102 | let
103 | x = (star.ph / 360) * 1000
104 | y = (star.th / 180) * 500
105 | ctx.setSourceRGBA(1, 1, 1, 1)
106 | ctx.newPath()
107 | ctx.arc(
108 | x,
109 | y,
110 | s,
111 | 0.0, 2.0*PI)
112 | ctx.fill()
113 | ctx.closePath()
114 |
115 | discard surface.writeToPng("tests/milkyway.png")
116 |
117 |
118 | proc drawStars3d() =
119 |
120 | # 3d
121 | var
122 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
123 | ctx = surface.create()
124 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 0.75)
125 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
126 | ctx.fill()
127 |
128 | ctx.translate(500, 500)
129 |
130 | var lookAtPos: Vec3
131 | for star in brightStars:
132 | if star.otherName == "Polaris":
133 | lookAtPos = star.pos
134 |
135 | var proj = perspective(40, 1, 0.01, 20)
136 | var mat = lookAt(-lookAtPos, vec3(0,0,0), vec3(0,0,1))
137 | #var mat = rotateX(0.toRad)
138 | for star in brightStars:
139 | var pos = proj * mat * star.pos
140 | pos.x /= pos.z
141 | pos.y /= pos.z
142 | if pos.z < 0: continue
143 | #if "UMi" notin star.name and "UMa" notin star.name: continue
144 | var s = 10/(star.luma + 1.44 + 1)
145 | ctx.setSourceRGBA(1, 1, 1, 1)
146 |
147 | ctx.newPath()
148 | ctx.arc(
149 | pos.x*500,
150 | pos.y*500,
151 | s,
152 | 0.0, 2.0*PI)
153 | ctx.fill()
154 | ctx.closePath()
155 |
156 | if star.otherName in ["Polaris", "Pherkad", "Kochab", "Yildun", "Kochab"]: #, "Dubhe", "Merak", "Megrez", "Phecda", "Alioth", "Alcor", "Mizar", "Alkaid"]:
157 | ctx.setSourceRGBA(1, 0, 0, 0.5)
158 | ctx.newPath()
159 | ctx.arc(
160 | pos.x*500,
161 | pos.y*500,
162 | 10,
163 | 0.0, 2.0*PI)
164 | ctx.stroke()
165 | ctx.closePath()
166 |
167 | if star.otherName in ["Dubhe", "Merak", "Megrez", "Phecda", "Alioth", "Alcor", "Mizar", "Alkaid"]:
168 | ctx.setSourceRGBA(0, 1, 0, 0.5)
169 | ctx.newPath()
170 | ctx.arc(
171 | pos.x*500,
172 | pos.y*500,
173 | 10,
174 | 0.0, 2.0*PI)
175 | ctx.stroke()
176 | ctx.closePath()
177 |
178 | discard surface.writeToPng("tests/brightstars3d.png")
179 |
180 |
181 | proc analyzeImage() =
182 |
183 | const keepBrightSpots = 50
184 |
185 | var img = loadImage("tests/bigdipper.png")
186 | var mipmaps: seq[Image]
187 | while img.width > 50 and img.height > 50:
188 | mipmaps.add img
189 | img = img.minifyBy2()
190 | print img.width
191 | echo "minified"
192 |
193 | var
194 | image = imageSurfaceCreateFromPng("tests/bigdipper.png")
195 | surface = imageSurfaceCreate(FORMAT_ARGB32, image.getWidth, image.getHeight)
196 | ctx = surface.create()
197 |
198 | ctx.setSource(image, 0, 0)
199 | ctx.paint()
200 |
201 | ctx.selectFontFace("Sans", FONT_SLANT_NORMAL, FONT_WEIGHT_NORMAL)
202 | ctx.setFontSize(12.0)
203 |
204 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 0.75)
205 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
206 | ctx.fill()
207 |
208 | ctx.setSourceRGBA(1, 1, 1, 1)
209 | ctx.setLineWidth(1)
210 |
211 | proc luma(rgba: ColorRGBA): float =
212 | return (float(rgba.r)/255.0 + float(rgba.g)/255.0 + float(rgba.b)/255.0)/3.0
213 |
214 | type Spot = object
215 | x, y: int
216 | luma: float
217 |
218 | proc pos(spot: Spot): Vec2 =
219 | vec2(float spot.x, float spot.y)
220 |
221 | proc `pos=`(spot: var Spot, pos: Vec2) =
222 | spot.x = int pos.x
223 | spot.y = int pos.y
224 |
225 | # find the brighest spot the the lowest mip level
226 | var brightestN: seq[Spot]
227 | proc insertSort(brightest: var seq[Spot], spot: Spot, num: int) =
228 | for i, s in brightest:
229 | if spot.luma > s.luma:
230 | brightest.insert(spot, i)
231 | if brightest.len > num:
232 | brightest.setLen(num)
233 | return
234 | if brightest.len < num:
235 | brightest.add(spot)
236 | let mostBlurLevel = mipmaps.len - 1
237 | let mostBlur = mipmaps[^1]
238 | for x in 0 ..< mostBlur.width:
239 | for y in 0 ..< mostBlur.height:
240 | let spot = Spot(x:x, y:y, luma:luma(mostBlur.getRgba(x, y)))
241 | brightestN.insertSort(spot, keepBrightSpots)
242 |
243 |
244 | # adjust each spot in accuracy as we walk up the mip levels
245 | for spot in brightestN.mitems:
246 | for mipLevel in 0.. spotA.luma:
274 | spotA.luma = spotB.luma
275 | if merge:
276 | merged.add spotA
277 |
278 | brightestN = merged
279 |
280 | for spot in brightestN:
281 |
282 | ctx.newPath()
283 | ctx.setLineWidth(spot.luma*2)
284 | ctx.arc(
285 | float spot.x,
286 | float spot.y,
287 | 10,
288 | 0.0, 2.0*PI)
289 | ctx.stroke()
290 | ctx.closePath()
291 |
292 | # ctx.save()
293 | # ctx.moveTo(float spot.x + 15, float spot.y)
294 | # ctx.showText("Luma: " & $spot.luma)
295 | # ctx.restore()
296 |
297 | print brightestN.len
298 | discard surface.writeToPng("tests/bigdipper_stars.png")
299 |
300 |
301 | var
302 | thisTri: StarTri
303 | thisTri.aID = 0
304 | thisTri.bID = 1
305 | thisTri.cID = 2
306 | thisTri.ab = dist(brightestN[0].pos, brightestN[1].pos)
307 | thisTri.bc = dist(brightestN[1].pos, brightestN[2].pos)
308 | thisTri.ca = dist(brightestN[2].pos, brightestN[0].pos)
309 | thisTri.normalize()
310 |
311 | print thisTri
312 | var posTrans = -brightestN[thisTri.aID].pos + vec2(float surface.getWidth, float surface.getHeight) / 2.0
313 | print posTrans
314 | for b in brightestN.mitems:
315 | b.pos = (b.pos + posTrans)
316 |
317 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1)
318 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
319 | ctx.fill()
320 |
321 | ctx.save()
322 | ctx.setSource(image, 0, 0)
323 | ctx.paint()
324 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 0.75)
325 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
326 | ctx.fill()
327 | ctx.restore()
328 |
329 | ctx.translate(-posTrans.x, -posTrans.y)
330 |
331 | ctx.setSourceRGBA(1, 1, 1, 1)
332 | ctx.setLineWidth(1)
333 | for i, id in [thisTri.aID, thisTri.bID, thisTri.cID]:
334 | let spot = brightestN[id]
335 | ctx.newPath()
336 | print spot
337 | ctx.arc(
338 | float spot.x,
339 | float spot.y,
340 | 10,
341 | 0.0, 2.0*PI)
342 | ctx.stroke()
343 | ctx.closePath()
344 | ctx.save()
345 | ctx.moveTo(float spot.x + 15, float spot.y)
346 | ctx.showText(" #" & $i)
347 | ctx.restore()
348 |
349 | for spot in brightestN:
350 | ctx.newPath()
351 | ctx.arc(
352 | float spot.x,
353 | float spot.y,
354 | 5,
355 | 0.0, 2.0*PI)
356 | ctx.stroke()
357 | ctx.closePath()
358 |
359 | var goodPixDist = float(max(surface.getWidth, surface.getHeight)) * 0.01
360 | var bestError = 1000.0
361 | var bestMatches = 0
362 | var bestMat: Mat4
363 | var bestTri: StarTri
364 | for tri in starTris:
365 |
366 | let minError = pow(thisTri.bc - tri.bc, 2) + pow(thisTri.ca - tri.ca, 2)
367 | print minError
368 | if true or minError < 0.8:
369 | bestError = minError
370 | # solve for matrix that:
371 | # brightestN[tri.aID].pos == mat * brightStars[tri.aID]
372 | # brightestN[tri.aID].pos == mat * brightStars[tri.aID]
373 | # brightestN[tri.aID].pos == mat * brightStars[tri.aID]
374 |
375 | # look at first star
376 | var mat = lookAt(brightStars[tri.aID].pos, vec3(0,0,0), vec3(0,0,1))
377 |
378 |
379 | # make sure rotation between first and second screen spot
380 | # match the rotation between first and second star
381 | let
382 | posA = mat * brightStars[tri.aID].pos
383 | posB = mat * brightStars[tri.bID].pos
384 | starAngle = arctan2(posB.y - posA.y, posB.x - posA.x)
385 | spotA = brightestN[thisTri.aID].pos
386 | spotB = brightestN[thisTri.bID].pos
387 | spotAngle = arctan2(spotB.y - spotA.y, spotB.x - spotA.x)
388 | mat = rotateZ(starAngle - spotAngle) * mat
389 |
390 | # make sure that the distance between first and second spot
391 | # match the distance between frist and second star
392 | let zoom = dist(brightestN[thisTri.aID].pos, brightestN[thisTri.bID].pos)
393 | let zoom2 = dist(mat * brightStars[tri.aID].pos, mat * brightStars[tri.bID].pos)
394 | mat = scaleMat((zoom/zoom2)) * mat
395 |
396 | # translate the stars into screen center
397 | mat = translate(vec3(float(surface.getWidth)/2.0, float(surface.getHeight)/2.0, 0.0)) * mat
398 |
399 | # compute error of the thrid star and spot
400 |
401 | var error = dist((mat * brightStars[tri.cID].pos).xy, brightestN[thisTri.cID].pos)
402 |
403 | if error < goodPixDist:
404 | # test all stars against bright spots
405 | var matches = 0
406 | for star in brightStars:
407 | var pos = mat * star.pos
408 | for spot in brightestN:
409 | if dist(spot.pos, pos.xy) < goodPixDist:
410 | inc matches
411 | if matches > bestMatches:
412 | bestMatches = matches
413 | bestMat = mat
414 | bestTri = tri
415 | print bestMatches
416 |
417 | for i, id in [bestTri.aId, bestTri.bId, bestTri.cId]:
418 | let star = brightStars[id]
419 | var pos = bestMat * star.pos
420 | ctx.setSourceRGBA(0, 1, 0, 1)
421 | ctx.newPath()
422 | ctx.arc(
423 | pos.x,
424 | pos.y,
425 | goodPixDist/2,
426 | 0.0, 2.0*PI)
427 | ctx.stroke()
428 | ctx.closePath()
429 | ctx.save()
430 | ctx.moveTo(float pos.x, float pos.y - 20)
431 | ctx.showText($i)
432 | ctx.restore()
433 |
434 | for star in brightStars:
435 | var pos = bestMat * star.pos
436 | ctx.setSourceRGBA(1, 0, 0, 1)
437 | ctx.newPath()
438 | ctx.arc(
439 | pos.x,
440 | pos.y,
441 | goodPixDist,
442 | 0.0, 2.0*PI)
443 | ctx.stroke()
444 | ctx.closePath()
445 | if star.otherName != "":
446 | ctx.save()
447 | ctx.moveTo(float pos.x + 15, float pos.y)
448 | ctx.showText(" " & star.otherName)
449 | ctx.restore()
450 |
451 | discard surface.writeToPng("tests/matching.png")
452 |
453 |
454 |
455 |
456 | readBrightStars()
457 | drawMilkyWay()
458 | # drawStars3d()
459 | # genStarTris()
460 | # analyzeImage()
--------------------------------------------------------------------------------
/src/orbits/elements.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "name": "mercury",
5 | "o": 48.10434602754433,
6 | "i": 7.003087466401921,
7 | "w": 27.78719571338938,
8 | "a": 0.4000255039788957,
9 | "e": 0.2201599657730014,
10 | "m": 334.788894542372,
11 | "n": 3.89819558905566
12 | },
13 | {
14 | "id": 2,
15 | "name": "venus",
16 | "o": 76.97669439756598,
17 | "i": 3.393108149410717,
18 | "w": 58.35621153448815,
19 | "a": 0.7182208678326626,
20 | "e": 0.01382589661308556,
21 | "m": 131.0010390214528,
22 | "n": 1.620341792975639
23 | },
24 | {
25 | "id": 3,
26 | "name": "earth-moon",
27 | "o": 289.3646992556555,
28 | "i": 0.0009582336890288729,
29 | "w": 187.9363761157968,
30 | "a": 1.000825172761707,
31 | "e": 0.01720051160494004,
32 | "m": 343.5828739041493,
33 | "n": 0.9850447188992955
34 | },
35 | {
36 | "id": 4,
37 | "name": "mars",
38 | "o": 49.64825348948091,
39 | "i": 1.851665869965064,
40 | "w": 287.4131338858448,
41 | "a": 1.533553293577183,
42 | "e": 0.09573753010619797,
43 | "m": 36.28542603422069,
44 | "n": 0.5193339756606363
45 | },
46 | {
47 | "id": 5,
48 | "name": "jupiter",
49 | "o": 100.4047724660621,
50 | "i": 1.305303896867871,
51 | "w": 273.6392934603785,
52 | "a": 5.19903163177084,
53 | "e": 0.0481019312121747,
54 | "m": 189.9816717886359,
55 | "n": 0.08307878022277117
56 | },
57 | {
58 | "id": 6,
59 | "name": "saturn",
60 | "o": 113.7660922435914,
61 | "i": 2.485904803750773,
62 | "w": 338.5348887046594,
63 | "a": 9.543087681962577,
64 | "e": 0.05473491200083265,
65 | "m": 310.8086659014555,
66 | "n": 0.03344073922859368
67 | },
68 | {
69 | "id": 7,
70 | "name": "uranus",
71 | "o": 74.01321376825706,
72 | "i": 0.7735080752889525,
73 | "w": 96.96320320192272,
74 | "a": 19.1872323556339,
75 | "e": 0.04707524224059415,
76 | "m": 13.72353476377218,
77 | "n": 0.01173406114563247
78 | },
79 | {
80 | "id": 8,
81 | "name": "neptune",
82 | "o": 131.7910151155089,
83 | "i": 1.76979707258509,
84 | "w": 273.7475287397057,
85 | "a": 30.07248540697402,
86 | "e": 0.008462720353253175,
87 | "m": 193.7953393791112,
88 | "n": 0.005980090506368096
89 | },
90 | {
91 | "id": 9,
92 | "name": "pluto-charon",
93 | "o": 110.3088877251087,
94 | "i": 17.14043880694397,
95 | "w": 113.7898310825914,
96 | "a": 39.48268090528783,
97 | "e": 0.2488884578150434,
98 | "m": 331.2789327899312,
99 | "n": 0.003975440882543604
100 | }
101 | ]
--------------------------------------------------------------------------------
/src/orbits/featuredb.nim:
--------------------------------------------------------------------------------
1 | ## Downloads planet feature (such as craters) with names and location from planetarynames.wr.usgs.gov
2 |
3 | import strformat, strutils
4 | import utils
5 |
6 | var url = (&"""https://planetarynames.wr.usgs.gov/SearchResults?
7 | feature=
8 | &system=
9 | &target=
10 | &featureType=
11 | &northernLatitude=
12 | &southernLatitude=
13 | &westernLongitude=
14 | &easternLongitude=
15 | &wellKnownText=
16 | &minFeatureDiameter=
17 | &maxFeatureDiameter=
18 | &beginDate=
19 | &endDate=
20 | &approvalStatus=
21 | &continent=
22 | ðnicity=
23 | &reference=
24 | &sort_asc=false
25 | &sort_column=diameter
26 | &is_positive_east=true
27 | &is_0_360=true
28 | &is_planetographic=false
29 | &displayType=CSV
30 | &pageStart=0
31 | &resultsPerPage=50
32 | &featureIDColumn=true
33 | &featureNameColumn=true
34 | &cleanFeatureNameColumn=true
35 | &targetColumn=true
36 | &diameterColumn=true
37 | ¢erLatLonColumn=true
38 | &latLonColumn=true
39 | &coordSystemColumn=true
40 | &contEthColumn=true
41 | &featureTypeColumn=true
42 | &featureTypeCodeColumn=true
43 | &quadColumn=true
44 | &approvalStatusColumn=true
45 | &approvalDateColumn=true
46 | &referenceColumn=true
47 | &originColumn=true
48 | &additionalInfoColumn=true
49 | &lastUpdatedColumn=true
50 | """).replace(" ", "").replace("\n", "")
51 |
52 | downloadFileIfNotExists(url, "db/feature.csv")
53 | writeFile("db/feature.csv", readFile("db/feature.csv").strip())
54 |
--------------------------------------------------------------------------------
/src/orbits/horizon.nim:
--------------------------------------------------------------------------------
1 | import net, streams, strutils, os, strutils, strformat
2 | import vmath, print
3 | import simple
4 |
5 |
6 | type
7 | HorizonClient* = ref object
8 | socket: Socket
9 | debug*: bool # turn on debug spam
10 |
11 | proc cutBy(main, s1, s2:string): string =
12 | var starts = main.find(s1) + s1.len
13 | var ends = main.rfind(s2)
14 | if starts == -1:
15 | quit("can't find s1 " & s1)
16 | if ends == -1:
17 | quit("can't find s2 " & s2)
18 | return main[starts.. "):
82 | send($targetId)
83 | if "Select ..." in lastLine and promt(": "):
84 | send("E")
85 | if promt("Observe, Elements, Vectors [o,e,v,?] : "):
86 | send(format)
87 |
88 | if cordinates != "":
89 | # rotation
90 | if "Coordinate" in lastLine and "center" in lastLine and promt("] : "):
91 | send("c@" & $targetId)
92 | # tons of stuff don't have rotation information, just skip it then
93 | if backLines.len > 3:
94 | if "Cannot find station file" in backLines[^3] or "No rotational model for center body" in backLines[^3] or "Cannot find central body matching" in backLines[^3]:
95 | send "x"
96 | return ""
97 | if "Cylindrical or Geodetic input" in lastLine and promt("] : "):
98 | send("g")
99 | if "Specify geodetic" in lastLine and promt("} : "):
100 | send(cordinates)
101 | if "Confirm selected station" in lastLine and promt("--> "):
102 | send("y")
103 | else:
104 | # position
105 | if "Coordinate" in lastLine and "center" in lastLine and promt("] : "):
106 | send("@" & $observerId)
107 |
108 | if " Use previous center" in lastLine and promt("] : "):
109 | send("n")
110 | if promt("Reference plane [eclip, frame, body ] : "):
111 | #send("eclip")
112 | send(reference)
113 |
114 | if promt("] : ") and "Starting TDB [>=" in lastLine:
115 | send(fromTime)
116 | if promt("] : ") and "Ending TDB [<=" in lastLine:
117 | send(toTime)
118 | if promt("Output interval [ex: 10m, 1h, 1d, ? ] : "):
119 | send(duration)
120 | if promt("Accept default output [ cr=(y), n, ?] : "):
121 | send("y")
122 | if "Scroll & Page: space" in lastLine:
123 | hz.socket.send(" ")
124 | if promt(">>> Select... [A]gain, [N]ew-case, [F]tp, [M]ail, [R]edisplay, ? : "):
125 | var orbitText = backBuffer.cutBy(
126 | "$$SOE",
127 | "$$EOE")
128 |
129 | var text = orbitText.strip()
130 |
131 | for blk in backBuffer.split("*******************************************************************************"):
132 | if "Output units" in blk:
133 | text = blk & "*** START ***\n" & text
134 |
135 | var start = 0
136 | while true:
137 | var badBlock = text.find("\x1B[", start)
138 | if badBlock == -1:
139 | break
140 | var endBadBlock = text.find("\27[m\27[K\13\27[K", badBlock+2)
141 | start = endBadBlock + 3
142 | text.delete(badBlock, endBadBlock+10)
143 |
144 | writeFile(fileKey, text)
145 | if hz.debug:
146 | echo "cached data in:", fileKey
147 | result = text
148 | send("n")
149 | running = false
150 |
151 | proc parseOrbitalVectors*(data: string): seq[OrbitalVectors] =
152 | var unit = AU
153 | var header, text: string
154 | var parts = data.split("*** START ***\n")
155 | header = parts[0]
156 | assert parts.len == 2
157 | text = parts[1]
158 | if "Output units : KM-D" in header:
159 | unit = KM
160 |
161 | var orbitData = newSeq[OrbitalVectors]()
162 | var entry: OrbitalVectors
163 |
164 | var i = 0
165 | for line in text.split("\n"):
166 | if i mod 4 == 0:
167 | var oddTime : float64 = parseFloat(line[0..15])
168 | var time = Y2000 + (oddTime - J2000) * DAY
169 | entry.time = time
170 |
171 | if line.startsWith(" X"):
172 | var p = dvec3(
173 | parseFloat(line[4..25].strip()),
174 | parseFloat(line[30..51].strip()),
175 | parseFloat(line[56..77].strip()))
176 | p = p * unit
177 | entry.pos = p
178 |
179 | if line.startsWith(" VX"):
180 | var v = dvec3(
181 | parseFloat(line[4..25].strip()),
182 | parseFloat(line[30..51].strip()),
183 | parseFloat(line[56..77].strip()))
184 | v = v * unit / DAY
185 | entry.vel = v
186 | orbitData.add(entry)
187 |
188 | if line.startsWith("VX"):
189 | var v = dvec3(
190 | parseFloat(line[3..24].strip()),
191 | parseFloat(line[29..50].strip()),
192 | parseFloat(line[55..76].strip()))
193 | v = v * unit / DAY
194 | orbitData.add(entry)
195 |
196 | inc i
197 |
198 | return orbitData
199 |
200 |
201 | proc close*(hz: HorizonClient) =
202 | if hz.socket != nil:
203 | hz.socket.close()
204 |
205 |
206 | proc getOrbitalVectorsSeq*(
207 | hz: HorizonClient,
208 | fromTime: float64,
209 | toTime: float64,
210 | steps: int,
211 | targetId: int,
212 | observerId: int
213 | ): seq[OrbitalVectors] =
214 | let data = hz.getOrbitalData(
215 | "v",
216 | "JD " & $toJulianDate(fromTime),
217 | "JD " & $toJulianDate(toTime),
218 | $steps,
219 | targetId,
220 | observerId,
221 | ""
222 | )
223 | return parseOrbitalVectors(data)
224 |
225 | iterator pairwise[T](s: seq[T]): (T, T) =
226 | for i in 0 ..< s.len - 1:
227 | yield(s[i], s[i + 1])
228 |
229 | proc posAt*(entries: seq[OrbitalVectors], time: float64): DVec3 =
230 | for a, b in entries.pairwise:
231 | if time >= a.time and time < b.time:
232 | let diff = (time - a.time) / (b.time - a.time)
233 | return lerp(a.pos, b.pos, diff)
234 |
235 | proc getOrbitalVectors*(
236 | hz: HorizonClient,
237 | time: float64,
238 | targetId: int,
239 | observerId: int
240 | ): OrbitalVectors =
241 | let data = hz.getOrbitalData(
242 | "v",
243 | "JD " & $toJulianDate(time),
244 | "JD " & $toJulianDate(time + DAY),
245 | "2",
246 | targetId,
247 | observerId,
248 | ""
249 | )
250 | return parseOrbitalVectors(data)[0]
251 |
252 |
253 | proc parseOrbitalElements*(data: string): seq[OrbitalElements] =
254 | var unit = AU
255 | var header, text: string
256 | var parts = data.split("*** START ***\n")
257 | header = parts[0]
258 | assert parts.len == 2
259 | text = parts[1]
260 | if "Output units : KM-D" in header:
261 | unit = KM
262 |
263 | var orbitData = newSeq[OrbitalElements]()
264 | var entry: OrbitalElements
265 |
266 | var i = 0
267 | var
268 | EC, QR, IN, OM, W, Tp, N, MA, TA, A, AD, PR: float
269 |
270 | for line in text.split("\n"):
271 | if i mod 5 == 0:
272 | var oddTime : float64 = parseFloat(line[0..15])
273 | var time = Y2000 + (oddTime - J2000) * DAY
274 | #entry.time = time
275 |
276 | if line.startsWith(" EC="):
277 | EC = parseFloat(line[4..25].strip())
278 | QR = parseFloat(line[30..51].strip())
279 | IN = parseFloat(line[56..77].strip())
280 |
281 | if line.startsWith(" OM="):
282 | OM = parseFloat(line[4..25].strip())
283 | W = parseFloat(line[30..51].strip())
284 | Tp = parseFloat(line[56..77].strip())
285 |
286 | if line.startsWith(" N ="):
287 | N = parseFloat(line[4..25].strip())
288 | MA = parseFloat(line[30..51].strip())
289 | TA = parseFloat(line[56..77].strip())
290 |
291 | if line.startsWith(" A ="):
292 | A = parseFloat(line[4..25].strip())
293 | AD = parseFloat(line[30..51].strip())
294 | PR = parseFloat(line[56..77].strip())
295 |
296 | entry.o = OM # longitude of the ascending node
297 | entry.i = IN # inclination to the ecliptic (plane of the Earth's orbit)
298 | entry.w = W # argument of perihelion
299 | entry.a = A # semi-major axis, or mean distance from Sun
300 | entry.e = EC # eccentricity (0=circle, 0-1=ellipse, 1=parabola)
301 | entry.m = MA # mean anomaly (0 at perihelion increases uniformly with time)
302 | entry.n = N # mean motion
303 |
304 | orbitData.add entry
305 |
306 | inc i
307 |
308 | return orbitData
309 |
310 |
311 | proc getOrbitalElementsSeq*(
312 | hz: HorizonClient,
313 | fromTime: float64,
314 | toTime: float64,
315 | steps: int,
316 | targetId: int,
317 | observerId: int
318 | ): seq[OrbitalElements] =
319 | ## Get a sequence of OrbitalElements
320 | let data = hz.getOrbitalData(
321 | "e",
322 | "JD " & $toJulianDate(fromTime),
323 | "JD " & $toJulianDate(toTime),
324 | $steps,
325 | targetId,
326 | observerId,
327 | ""
328 | )
329 | return parseOrbitalElements(data)
330 |
331 |
332 | proc getOrbitalElements*(
333 | hz: HorizonClient,
334 | time: float64,
335 | targetId: int,
336 | observerId: int
337 | ): OrbitalElements =
338 | ## Get a single set of OrbitalElements at a given time
339 | let data = hz.getOrbitalData(
340 | "e",
341 | "JD " & $toJulianDate(time),
342 | "JD " & $toJulianDate(time+DAY),
343 | "2",
344 | targetId,
345 | observerId,
346 | ""
347 | )
348 | return parseOrbitalElements(data)[0]
349 |
350 |
351 | proc getRadius*(
352 | hz: HorizonClient,
353 | time: float64,
354 | targetId: int
355 | ): float =
356 | ## Gets the radius of the body in meters
357 | let data = hz.getOrbitalData(
358 | "v",
359 | "JD " & $toJulianDate(time),
360 | "JD " & $toJulianDate(time+DAY),
361 | "2",
362 | targetId,
363 | targetId,
364 | "0,0,0"
365 | )
366 | var ov = parseOrbitalVectors(data)[0]
367 | return ov.pos.length
368 |
369 |
370 | proc getRotationAxis*(
371 | hz: HorizonClient,
372 | time: float64,
373 | targetId: int
374 | ): DVec3 =
375 | ## Get a normalized vector that the body rotation around.
376 | let data = hz.getOrbitalData(
377 | "v",
378 | "JD " & $toJulianDate(time),
379 | "JD " & $toJulianDate(time+DAY),
380 | "2",
381 | targetId,
382 | targetId,
383 | "0,-90,0",
384 | "eclip"
385 | )
386 | var ov = parseOrbitalVectors(data)[0]
387 | return ov.pos.normalize()
388 |
389 |
390 | proc getRotationAngularSpeed*(
391 | hz: HorizonClient,
392 | time: float64,
393 | targetId: int
394 | ): float =
395 | ## Get the rotation angular speed in rad/second
396 | let timeScale: float = 60 * 60
397 | let data = hz.getOrbitalData(
398 | "v",
399 | "JD " & $toJulianDate(time),
400 | "JD " & $toJulianDate(time + timeScale * 25),
401 | "1h",
402 | targetId,
403 | targetId,
404 | "0,0,0"
405 | )
406 | for i in 0..<24:
407 | let
408 | vec1 = parseOrbitalVectors(data)[i].pos
409 | vec2 = parseOrbitalVectors(data)[i+1].pos
410 | result += vec1.angle(vec2) / timeScale
411 | result = result / 24
412 |
--------------------------------------------------------------------------------
/src/orbits/simple.nim:
--------------------------------------------------------------------------------
1 | import math, tables, json
2 | import vmath
3 |
4 | type
5 | OrbitalElements* = object
6 | id*: int # JPL horizon ID
7 | name*: string # object name
8 | o*: float64 # longitude of the ascending node
9 | i*: float64 # inclination to the ecliptic (plane of the Earth's orbit)
10 | w*: float64 # argument of perihelion
11 | a*: float64 # semi-major axis, or mean distance from Sun
12 | e*: float64 # eccentricity (0=circle, 0-1=ellipse, 1=parabola)
13 | m*: float64 # mean anomaly (0 at perihelion increases uniformly with time)
14 | n*: float64 # Mean motion
15 |
16 |
17 | OrbitalVectors* = object
18 | pos*: DVec3 # position
19 | vel*: DVec3 # velocity
20 | time*: float64 # time
21 |
22 |
23 | const Y2000* = 946684800.0
24 | const J2000* = 2451544.5
25 | const AU* = 149597870700.0
26 | const KM* = 1000.0
27 | const DAY* = 60*60*24
28 | const JULIAN_YEAR* = 365.25 * DAY
29 | const JULIAN_CENTURY* = 36525 * DAY
30 | const C* = 299792458
31 | const G* = 6.67259E-11
32 |
33 | proc toJulianDate*(time: float64): float64 =
34 | (time - Y2000) / DAY + J2000
35 |
36 | const elementsData = staticRead("elements.json")
37 | var simpleElements* = parseJson(elementsData).to(seq[OrbitalElements])
38 |
39 | proc period*(oe: OrbitalElements): float64 =
40 | return 365.2568984 * pow(oe.a, 1.5) * 24*60*60
41 |
42 | proc rev*(x: float64): float64 =
43 | var rv = x - round(x / 360.0) * 360.0
44 | if rv < 0.0:
45 | rv = rv + 360.0
46 | return rv
47 |
48 | proc toRadians*(deg: float): float =
49 | return PI * deg / 180.0
50 |
51 | proc toDegrees*(rad: float): float =
52 | return rev(180.0 * rad / PI)
53 |
54 | proc posAt*(orbitalElements: OrbitalElements, time: float64): DVec3 =
55 | var d = time / (24*60*60)
56 |
57 | var N = orbitalElements.o # (Long asc. node)
58 | var i = orbitalElements.i # (Inclination)
59 | var w = orbitalElements.w # (Arg. of perigee)
60 | var a = orbitalElements.a # (Mean distance)
61 | var e = orbitalElements.e # (Eccentricity)
62 | var M = orbitalElements.m # (Mean anomaly)
63 | M += orbitalElements.n * d # (Mean motion)
64 |
65 | # Normalize angles
66 | N = rev(N)
67 | i = rev(i)
68 | w = rev(w)
69 | M = rev(M)
70 |
71 | # Compute the eccentric anomaly E from the mean anomaly M and from the eccentricity e (E and M in degrees):
72 | var E = M + (180/PI) * e * sin(toRadians(M)) * (1.0 + e * cos(toRadians(M)))
73 | var error = 1.0
74 | while error > 0.005:
75 | var E1 = E - (E - (180/PI) * e * sin(toRadians(E)) - M) / (1 - e * cos(toRadians(E)))
76 | error = abs(E - E1)
77 | E = E1
78 |
79 | # Then compute the body's distance r and its true anomaly v from:
80 | var x = a * (cos(toRadians(E)) - e)
81 | var y = a * (sqrt(1.0 - e*e) * sin(toRadians(E)))
82 |
83 | # Then we convert this to distance and true anonaly:
84 | var r = sqrt(x*x + y*y)
85 | var v = toDegrees(arctan2(y, x))
86 |
87 | var n_rad = toRadians(N)
88 | var xw_rad = toRadians(v + w)
89 | var i_rad = toRadians(i)
90 |
91 | # To compute the position in ecliptic coordinates, we apply these formulae:
92 | var xeclip = r * ( cos(n_rad) * cos(xw_rad) - sin(n_rad) * sin(xw_rad) * cos(i_rad) )
93 | var yeclip = r * ( sin(n_rad) * cos(xw_rad) + cos(n_rad) * sin(xw_rad) * cos(i_rad) )
94 | var zeclip = r * sin(xw_rad) * sin(i_rad)
95 |
96 | var RA = toDegrees(arctan2(yeclip, xeclip))
97 | var Decl = toDegrees(arcsin(zeclip / r))
98 |
99 | return dvec3(xeclip, yeclip, zeclip) * AU
100 |
--------------------------------------------------------------------------------
/src/orbits/simulate.nim:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/src/orbits/simulate.nim
--------------------------------------------------------------------------------
/src/orbits/solarbodydb.nim:
--------------------------------------------------------------------------------
1 | ## Solor body downloads differnet databases from JPL Propulsion LIbratory
2 | ## And combines them into one.
3 |
4 |
5 | import httpclient, json, os, re, strutils, htmlparser, xmltree, sequtils
6 | import simple, utils
7 | import print
8 |
9 | type
10 | Body = object
11 | id: int
12 | parentId: int
13 | name: string
14 | meanRadiusM: float
15 | massKg: float
16 | albedo: float
17 |
18 | gm: float # G * mass
19 | equatorialRadiusM: float
20 | densityKgM3: float
21 | siderealRotationPeriodS: float
22 | orbitRotationPeriodS: float
23 | equatorialGravity: float
24 | escapeVelocityM: float
25 |
26 |
27 | var bodies: seq[Body]
28 |
29 |
30 | proc constantsParser*() =
31 | downloadFileIfNotExists("https://ssd.jpl.nasa.gov/?constants","cache/constants.html")
32 | var html = readFile("cache/constants.html").replace(" ", " ").replace("±","+-").replace("-11 kg-1 m3 s-2 ", "")
33 | var values = html.findAll(re"""(.*) | """)
34 | proc fix(a: string): string = a.replace("""""", "").replace(" | ", "").strip()
35 | for i, v in values:
36 | if i + 1 >= values.len: break
37 | let
38 | name = fix(v)
39 | value = fix(values[i+1])
40 | if name in [
41 | "speed of light",
42 | "Julian day",
43 | "Julian year",
44 | "Julian century",
45 | "astronomical unit",
46 | "mean sidereal day",
47 | "sidereal year",
48 | "gravitational constant",
49 | ]:
50 | echo name, " = ", value
51 |
52 |
53 | proc planetsParser*() =
54 | downloadFileIfNotExists("https://ssd.jpl.nasa.gov/?planet_phys_par","cache/planets.html")
55 | var html = readFile("cache/planets.html")
56 | var table = html.cutBy(""" (km s-1) |
57 | """,
58 | """References
""")
59 | var text = table.replacef(re"<(\w*)[^>]*>", "")
60 | text = text.replace(" ", " ").replace("±","+-")
61 | text = text.replace(re"\s+", " ")
62 | text = text.replacef(re"([A-Z][a-z]+)", "\n$1")
63 |
64 | proc p(num: string): float =
65 | parseFloat(num.split("+-")[0])
66 |
67 | var body = Body()
68 | body.id = 0
69 | body.parentId = 0
70 | body.name = "Solar System Barycenter (SSB)"
71 | body.massKg = 1.989E30
72 | body.gm = body.massKg * G
73 | bodies.add(body)
74 |
75 | body = Body()
76 | body.id = 10
77 | body.parentId = 0
78 | body.name = "Sun"
79 | body.massKg = 1.989E30
80 | body.gm = body.massKg * G
81 | body.equatorialRadiusM = 6.957E8
82 | body.meanRadiusM = 6.957E8
83 | body.siderealRotationPeriodS = 25.05*24*60*60
84 | bodies.add(body)
85 |
86 | var planetId = 1
87 | for line in text.strip().splitLines():
88 | var args = line.split(" ")
89 | # barycenter
90 | var body = Body()
91 | body.id = planetId
92 | body.parentId = planetId
93 | body.name = args[0] & " Barycenter"
94 | if planetId == 3: body.name = "Earth-Moot Barycenter"
95 | if planetId == 9: body.name = "Pluto-Charon Barycenter"
96 | body.massKg = p(args[5]) * 1E+24
97 | body.gm = body.massKg * G
98 | body.orbitRotationPeriodS = p(args[11])*24*60*60*365.25
99 | bodies.add(body)
100 | inc planetId
101 |
102 | # body itself
103 | planetId = 1
104 | for line in text.strip().splitLines():
105 | var args = line.split(" ")
106 | var body = Body()
107 | body.id = planetId * 100 + 99
108 | body.parentId = planetId
109 | body.name = args[0]
110 | body.equatorialRadiusM = p(args[1])*1000
111 | body.meanRadiusM = p(args[3])*1000
112 | body.massKg = p(args[5]) * 1E+24
113 | body.gm = body.massKg * G
114 | body.densityKgM3 = p(args[7]) * 1000
115 | body.siderealRotationPeriodS = p(args[9])*24*60*60
116 | body.orbitRotationPeriodS = p(args[11])*24*60*60*365.25
117 | body.albedo = p(args[15])
118 | body.equatorialGravity = p(args[17])
119 | body.escapeVelocityM = p(args[19]) * 1000
120 | bodies.add(body)
121 |
122 | inc planetId
123 |
124 |
125 | proc satellitesParser*() =
126 | downloadFileIfNotExists("https://ssd.jpl.nasa.gov/?sat_phys_par","cache/satellites.html")
127 | var html = readFile("cache/satellites.html")
128 | var table = html.cutBy("""JPL lunar constants and models technical document
129 | is also available.
130 |
""",
131 | """""")
132 | table = table.replace("", "name:")
133 | var text = table.replacef(re"<(\w*)[^>]*>", "")
134 | text = text.replace(" ", " ").replace(" ", " ").replace("±","+-")
135 | text = text.replace(re"\s+", " ")
136 | text = text.replace("name:", "\n")
137 | text = text.replacef(re"\]([^\]]*GM \(km3/sec2\))", "]\n---- $1")
138 | text = "---- " & text
139 | #text = text.replacef(re"([A-Z][a-z//\d]+)", "\n$1")
140 | #var moons = text.split("GeometricAlbedo")
141 | echo text
142 |
143 | var
144 | planetId = 2
145 | moonId = 1
146 |
147 | for line in text.strip().splitLines():
148 | if line.startsWith("----"):
149 | inc planetId
150 | moonId = 1
151 | continue
152 | var args = line.split(" ")
153 | if args[0].startsWith("S/20"):
154 | args[0].add " " & args[1]
155 | args.delete(1)
156 |
157 | proc p(num: string): float =
158 | if num == "?": return 0
159 | parseFloat(num.split(re"(\+\-|\[)")[0])
160 | var body = Body()
161 | body.id = planetId * 100 + moonId
162 | body.parentId = planetId
163 | body.name = args[0]
164 | body.gm = p(args[1]) * 1000000000.0
165 | body.massKg = body.gm / G
166 | body.meanRadiusM = p(args[2])*1000
167 | body.densityKgM3 = p(args[3])*1000
168 | body.albedo = p(args[5])
169 | bodies.add(body)
170 | inc moonId
171 |
172 |
173 | proc smallBodyParser*() =
174 | var filePath = "db/smallBody.csv"
175 | if not existsFile(filePath):
176 | var client = newHttpClient()
177 | let url = "https://ssd.jpl.nasa.gov/sbdb_query.cgi"
178 | var data = newMultipartData()
179 | data["obj_group"] = "all"
180 | data["obj_kind"] = "all"
181 | data["obj_numbered"] = "all"
182 | data["OBJ_field"] = "0"
183 | data["OBJ_op"] = "0"
184 | data["OBJ_value"] = ""
185 | data["ORB_field"] = "0"
186 | data["ORB_op"] = "0"
187 | data["ORB_value"] = ""
188 | data["c_fields"] = "AbAeAtApArBgBhBiBjBkBlBmBnBoBr"
189 | data["table_format"] = "CSV"
190 | data["max_rows"] = "500"
191 | data["format_option"] = "full"
192 | data["query"] = "Generate Table"
193 | data[".cgifields"] = "format_option"
194 | data[".cgifields"] = "field_list"
195 | data[".cgifields"] = "obj_kind"
196 | data[".cgifields"] = "obj_group"
197 | data[".cgifields"] = "obj_numbered"
198 | data[".cgifields"] = "ast_orbit_class"
199 | data[".cgifields"] = "table_format"
200 | data[".cgifields"] = "OBJ_field_set"
201 | data[".cgifields"] = "ORB_field_set"
202 | data[".cgifields"] = "preset_field_set"
203 | data[".cgifields"] = "com_orbit_class"
204 | var csv = client.postContent(url, multipart=data)
205 | writeFile(filePath, csv)
206 | var csv = readFile(filePath)
207 | var i = 0
208 | for line in csv.splitLines()[0..100]:
209 | if i == 0:
210 | inc i
211 | continue
212 | var args = line.split(",")
213 | #echo args
214 | proc p(num: string): float =
215 | if num == "": return 0
216 | let num2 = num.split(re"[^\d.+-eE]")[0]
217 | if num2 == "": return 0
218 | parseFloat(num2)
219 |
220 | var body = Body()
221 | body.id = parseInt(args[0])
222 | body.name = args[1]
223 | body.gm = p(args[2]) * 1000000000.0
224 | body.massKg = body.gm / G
225 | body.meanRadiusM = p(args[3])/2*1000
226 | body.albedo = p(args[4])
227 | bodies.add(body)
228 |
229 | inc i
230 |
231 |
232 | proc writeBodyDb() =
233 | var s = "id,parentId,name,massKg,meanRadiusM,albedo,gm,equatorialRadiusM,densityKgM3,siderealRotationPeriodS,orbitRotationPeriodS,equatorialGravity,escapeVelocityM\n"
234 | for body in bodies:
235 | s.add $body.id & ","
236 | s.add $body.parentId & ","
237 | s.add body.name & ","
238 | s.add $body.massKg & ","
239 | s.add $body.meanRadiusM & ","
240 | s.add $body.albedo & ","
241 | s.add $body.gm & ","
242 | s.add $body.equatorialRadiusM & ","
243 | s.add $body.densityKgM3 & ","
244 | s.add $body.siderealRotationPeriodS & ","
245 | s.add $body.orbitRotationPeriodS & ","
246 | s.add $body.equatorialGravity & ","
247 | s.add $body.escapeVelocityM & ","
248 | s.add "\n"
249 | writeFile("db/bigBody.csv", s)
250 |
251 | #constantsParser()
252 | planetsParser()
253 | satellitesParser()
254 | smallBodyParser()
255 |
256 | writeBodyDb()
--------------------------------------------------------------------------------
/src/orbits/solvers.nim:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | func computeMachineEpsilon(): float =
4 | ## Return a number you can add to 1.0 and still get 1.0
5 | result = 1.0
6 | while true:
7 | result *= 0.5
8 | if (1.0 + result) == 1.0:
9 | break
10 |
11 | const
12 | GOLDEN_RATIO* = (1 + sqrt(5.0)) / 2
13 | MACHINE_EPSILON* = computeMachineEpsilon()
14 |
15 |
16 | proc isNaN*(x: float): bool = x.classify == fcNaN
17 | proc isInf*(x: float): bool = x.classify == fcInf
18 |
19 |
20 | proc newtonsRoot*(x0: float, f, df: proc(x: float): float): float =
21 | ## Finds the root of f(x) near x0 given df(x) = f'(x)
22 | var
23 | x0 = x0
24 | x = 0.0
25 | while true:
26 | x = x0 - f(x0) / df(x0)
27 | if isNaN(x) or abs(x - x0) < 1e-6: # Close enough
28 | return x
29 | x0 = x
30 |
31 |
32 | proc goldenSectionSearch*(x1, x2: float, f: proc(x: float): float, epsilon=MACHINE_EPSILON): float =
33 | # Finds the minimum of f(x) between x1 and x2. Returns x.
34 | # See: http://en.wikipedia.org/wiki/Golden_section_search
35 |
36 | let
37 | k = 2 - GOLDEN_RATIO
38 | var
39 | x3 = x2
40 | x2 = x1 + k * (x3 - x1)
41 | x1 = 0.0
42 | x = 0.0
43 |
44 | y2 = f(x2)
45 | y = 0.0
46 |
47 | while true:
48 | if (x3 - x2) > (x2 - x1):
49 | x = x2 + k * (x3 - x2)
50 | else:
51 | x = x2 - k * (x2 - x1)
52 |
53 | if (x3 - x1) <= (epsilon * (x2 + x)): # Close enough
54 | return (x3 + x1) / 2
55 |
56 | y = f(x)
57 | if y < y2:
58 | if (x3 - x2) > (x2 - x1):
59 | x1 = x2
60 | else:
61 | x3 = x2
62 | x2 = x
63 | y2 = y
64 | else:
65 | if (x3 - x2) > (x2 - x1):
66 | x3 = x
67 | else:
68 | x1 = x
--------------------------------------------------------------------------------
/src/orbits/spk.nim:
--------------------------------------------------------------------------------
1 | ## Reads SPICE SPK DAF files. SPK files have .bsp extention.
2 | ## You can download many of the SPK files here: https://naif.jpl.nasa.gov/pub/naif/
3 | ##
4 | ## Based on the work by arrieta:
5 | ## https://gist.githubusercontent.com/arrieta/c2b56f1e2277a6fede6d1afbc85095fb/raw/70c43dd28804be9ba9f7c604fe3ad2742f8fa753/spk.py
6 | ##
7 |
8 |
9 | import streams, print, puppy, strutils, os
10 | import vmath
11 | import simple
12 |
13 | type
14 | StateVecs = object
15 | pos: DVec3
16 | vel: DVec3
17 |
18 |
19 | iterator range(start, stop, step: int): int =
20 | var at = start
21 | if step < 0:
22 | while at > stop:
23 | yield at
24 | at += step
25 | else:
26 | while at < stop:
27 | yield at
28 | at += step
29 |
30 |
31 | proc chebyshev(order: int, x: float64, data: seq[float64]): float64 =
32 | # Evaluate a Chebyshev polynomial
33 | var
34 | two_x = 2 * x
35 | bkp2 = data[order]
36 | bkp1 = two_x * bkp2 + data[order - 1]
37 | for n in range(order - 2, 0, -1):
38 | let bk = data[n] + two_x * bkp1 - bkp2
39 | bkp2 = bkp1
40 | bkp1 = bk
41 | return data[0] + x * bkp1 - bkp2
42 |
43 |
44 | proc der_chebyshev(order: int, x: float64, data: seq[float64]): float64 =
45 | # Evaluate the derivative of a Chebyshev polynomial
46 | var
47 | two_x = 2 * x
48 | bkp2 = float64(order) * data[order]
49 | bkp1 = two_x * bkp2 + float64(order - 1) * data[order - 1]
50 | for n in range(order - 2, 1, -1):
51 | let bk = float64(n) * data[n] + two_x * bkp1 - bkp2
52 | bkp2 = bkp1
53 | bkp1 = bk
54 | return data[1] + two_x * bkp1 - bkp2
55 |
56 |
57 | type
58 | Spk = ref object
59 | records*: seq[Record]
60 | stream: Stream
61 |
62 | Record* = object
63 | etbeg*: float64
64 | etend*: float64
65 | target*: uint32
66 | observer*: uint32
67 | frame*: uint32
68 | rtype*: uint32
69 | rbeg*: uint32
70 | rend*: uint32
71 |
72 | proc readSpk*(filename: string): Spk =
73 |
74 | var spk = Spk()
75 |
76 | var f = openFileStream(filename)
77 | spk.stream = f
78 | # make sure this is in the right file format
79 | if f.readStr(7) != "DAF/SPK":
80 | quit("invalid SPK signature")
81 | f.setPosition(88)
82 | # make sure this is little endian
83 | if f.readStr(8) != "LTL-IEEE":
84 | quit("SPK file is not little endian")
85 | f.setPosition(8)
86 | # make sure number of float64 per record and number of uint32 per record match
87 | if f.readInt32() != 2:
88 | quit("SPK file float64 per record does not match")
89 | if f.readInt32() != 6:
90 | quit("SPK file number of uint32 per record match")
91 |
92 | f.setPosition(76)
93 | let
94 | fward = f.readInt32()
95 | bward = f.readInt32()
96 |
97 | # extract summary records
98 | let
99 | summaryOffset = (fward - 1) * 1024
100 | summarySize = 2 + (6 + 1) div 2 # integer division
101 | f.setPosition(summaryOffset)
102 |
103 | while true:
104 | # read record chunks
105 | let
106 | nxt = f.readFloat64()
107 | prv = f.readFloat64()
108 | nsum = f.readFloat64()
109 | for n in 0..")
137 | if tleFile.endsWith(".txt"):
138 | let tleFileUrl = "https://www.celestrak.com/NORAD/elements/" & tleFile
139 | echo tleFileUrl
140 | downloadFileIfNotExists(tleFileUrl, cacheDir / "" & tleFile)
141 | readTLE( cacheDir / "" & tleFile)
142 |
143 |
144 | proc heavensAbove(id: int) =
145 | downloadFileIfNotExists(&"https://www.heavens-above.com/satinfo.aspx?satid={id}", cacheDir / &"satinfo_{id}.html")
146 | var html = readFile(cacheDir / &"satinfo_{id}.html")
147 | var satInfo = SatInfo()
148 | for line in html.split("\n"):
149 |
150 | let satID = line.cutBy("", "")
151 | if satID.len > 0:
152 | #print satID
153 | satInfo.id = parseInt(satID)
154 | let cosparId = line.cutBy("", "").strip()
155 | if cosparId.len > 0:
156 | #print cosparId
157 | satInfo.cosparId = cosparId
158 | let name = line.cutBy("", "")
159 | if name.len > 0:
160 | #print name
161 | satInfo.name = name
162 | var orbit = line.cutBy("", "")
163 | if orbit.len > 0:
164 | orbit = orbit.replace(" ", " ")
165 | #print orbit
166 | satInfo.orbit = orbit
167 | if orbit.startsWith("decayed"):
168 | var orbitDecayedDate = orbit[8 .. ^1]
169 | #print orbitDecayedDate
170 | satInfo.orbitDecayedDate = orbitDecayedDate
171 | if orbit.startsWith("landed"):
172 | var landedDate = orbit[7 .. ^1]
173 | #print landedDate
174 | satInfo.landedDate = landedDate
175 | if " x " in orbit:
176 | let orbitParts = orbit.split(" ")
177 | var periapsis = orbitParts[2]
178 | var apoapsis = orbitParts[0]
179 | var inclination = orbitParts[4].replace("\xC2\xB0", "")
180 | #print periapsis, apoapsis, inclination
181 | satInfo.periapsis = parseFloat periapsis.replace(",", "")
182 | satInfo.apoapsis = parseFloat apoapsis.replace(",", "")
183 | satInfo.inclination = parseFloat inclination
184 |
185 | let category = line.cutBy("Category | ", " | ").strip()
186 | if category.len > 0:
187 | #print category
188 | satInfo.category = category
189 | let country = line.cutBy("Country/organisation of origin | ", " | ")
190 | if country.len > 0:
191 | #print country
192 | satInfo.country = country
193 | let launchDate = line.cutBy("", "")
194 | if launchDate.len > 0:
195 | #print launchDate
196 | satInfo.launchDate = launchDate
197 | var launchSite = line.cutBy("Launch site | ", " | ")
198 | if launchSite.len > 0:
199 | launchSite = launchSite.replace("
", "")
200 | #print launchSite
201 | satInfo.launchSite = launchSite
202 | let launchVehicle = line.cutBy("", "")
203 | if launchVehicle.len > 0:
204 | #print launchVehicle
205 | satInfo.launchVehicle = launchVehicle
206 | let description = line.cutBy("Description
")
207 | if description.len > 0:
208 | #print description
209 | satInfo.description = description
210 | let mass = line.cutBy("Mass ", " | ")
211 | if mass.len > 0:
212 | #print mass
213 | satInfo.mass = parseInt mass.split(" ")[0]
214 | var image = line.cutBy("
0:
216 | image = "https://www.heavens-above.com/" & image
217 | #print image
218 | satInfo.image = image
219 | var intrinsicBrightness = line.cutBy("Intrinsic brightness (Magnitude) ", " | ")
220 | if intrinsicBrightness.len > 0:
221 | if intrinsicBrightness == "?":
222 | intrinsicBrightness = ""
223 | #print intrinsicBrightness
224 | satInfo.intrinsicBrightness = intrinsicBrightness
225 | var maxBrightness = line.cutBy("Maximum brightness (Magnitude)", " | ")
226 | if maxBrightness.len > 0:
227 | #print maxBrightness
228 | satInfo.maxBrightness = maxBrightness
229 |
230 | print satInfo.id, satInfo.name
231 |
232 | satInfos[satInfo.id] = satInfo
233 |
234 |
235 | proc q(s: string): string =
236 | if "," in s or " " in s:
237 | return "\"" & s & "\""
238 |
239 | proc q(s: SomeNumber): string =
240 | $s
241 |
242 | proc writeSatelliteHistorical() =
243 | var data = "id,name,cosparId,orbit,orbitDecayedDate,landedDate,periapsis,apoapsis,inclination,category,country,launchDate,launchSite,launchVehicle,description,mass,image,intrinsicBrightness,maxBrightness\n"
244 | for id in toSeq(satInfos.keys).sorted:
245 | let satInfo = satInfos[id]
246 | data.add q satInfo.id
247 | data.add ","
248 | data.add q satInfo.name
249 | data.add ","
250 | data.add q satInfo.cosparId
251 | data.add ","
252 | data.add q satInfo.orbit
253 | data.add ","
254 | data.add q satInfo.orbitDecayedDate
255 | data.add ","
256 | data.add q satInfo.landedDate
257 | data.add ","
258 | data.add q satInfo.periapsis
259 | data.add ","
260 | data.add q satInfo.apoapsis
261 | data.add ","
262 | data.add q satInfo.inclination
263 | data.add ","
264 | data.add q satInfo.category
265 | data.add ","
266 | data.add q satInfo.country
267 | data.add ","
268 | data.add q satInfo.launchDate
269 | data.add ","
270 | data.add q satInfo.launchSite
271 | data.add ","
272 | data.add satInfo.launchVehicle
273 | data.add ","
274 | data.add q satInfo.description
275 | data.add ","
276 | data.add q satInfo.mass
277 | data.add ","
278 | data.add q satInfo.image
279 | data.add ","
280 | data.add q satInfo.intrinsicBrightness
281 | data.add ","
282 | data.add q satInfo.maxBrightness
283 | data.add "\n"
284 |
285 | writeFile("db/satelliteHistorical.csv", data)
286 |
287 |
288 | proc writeTLE() =
289 | var data = "satnum,name,group,classification,intldesg,year,epochdays,ndot,nddot,bstar,ephtype,elnum,inclo,nodeo,ecco,argpo,mo,no_kozai,revnum\n"
290 | for name in toSeq(tles.keys).sorted:
291 | let tle = tles[name]
292 | data.add $tle.satnum
293 | data.add ","
294 | data.add tle.name
295 | data.add ","
296 | data.add tle.group
297 | data.add ","
298 | data.add tle.classification
299 | data.add ","
300 | data.add tle.intldesg
301 | data.add ","
302 | data.add $tle.year
303 | data.add ","
304 | data.add $tle.epochdays
305 | data.add ","
306 | data.add $tle.ndot
307 | data.add ","
308 | data.add $tle.nddot
309 | data.add ","
310 | data.add $tle.bstar
311 | data.add ","
312 | data.add tle.ephtype
313 | data.add ","
314 | data.add $tle.elnum
315 | data.add ","
316 | data.add $tle.inclo
317 | data.add ","
318 | data.add $tle.nodeo
319 | data.add ","
320 | data.add $tle.ecco
321 | data.add ","
322 | data.add $tle.argpo
323 | data.add ","
324 | data.add $tle.mo
325 | data.add ","
326 | data.add $tle.no_kozai
327 | data.add ","
328 | data.add $tle.revnum
329 | data.add ","
330 | data.add $tle.ibexp
331 | data.add ","
332 | data.add $tle.revnum
333 | data.add "\n"
334 |
335 | writeFile("db/satellite.csv", data)
336 |
337 | data = "satnum,name,group,classification,intldesg\n"
338 | for name in toSeq(tles.keys).sorted:
339 | let tle = tles[name]
340 | data.add $tle.satnum
341 | data.add ","
342 | data.add tle.name
343 | data.add ","
344 | data.add tle.group
345 | data.add ","
346 | data.add tle.classification
347 | data.add ","
348 | data.add tle.intldesg
349 | data.add "\n"
350 | writeFile("db/satelliteNames.csv", data)
351 |
352 | # downloadTLEIndex()
353 | # writeTLE()
354 |
355 | for id in 1 .. 18723:
356 | #print "-------------"
357 | heavensAbove(id)
358 | writeSatelliteHistorical()
--------------------------------------------------------------------------------
/src/orbits/utils.nim:
--------------------------------------------------------------------------------
1 | import os, strutils, puppy
2 |
3 | proc cutBy*(text, startStr, endStr: string): string =
4 | let a = text.find(startStr)
5 | if a == -1:
6 | return
7 | let b = text.find(endStr, start=a + startStr.len)
8 | if b == -1:
9 | return
10 | return text[a + startStr.len ..< b]
11 |
12 |
13 | proc downloadFileIfNotExists*(url, filePath: string) =
14 | let dir = filePath.splitPath.head
15 | if not existsDir(dir):
16 | createDir(dir)
17 | if existsFile(filePath):
18 | return
19 | writeFile(puppy.fetch(url), filePath)
20 |
--------------------------------------------------------------------------------
/tests/all.nim:
--------------------------------------------------------------------------------
1 | import generateOrbitalElements
2 | import generateStats
3 | import orbitsHorizon
4 | import orbitsSimple
5 | import orbitsSpk
6 | import spacecraft
7 | #import starman
--------------------------------------------------------------------------------
/tests/bigdipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/bigdipper.png
--------------------------------------------------------------------------------
/tests/bigdipper_stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/bigdipper_stars.png
--------------------------------------------------------------------------------
/tests/brightstars3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/brightstars3d.png
--------------------------------------------------------------------------------
/tests/earthToMarsSim.nim:
--------------------------------------------------------------------------------
1 | import strformat
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64, random, math
4 | import print
5 |
6 |
7 | var
8 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
9 | ctx = surface.create()
10 |
11 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
12 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
13 | ctx.fill()
14 |
15 | let scale = 250.0
16 |
17 | ctx.translate(500, 500)
18 | ctx.scale(scale, scale)
19 | ctx.setLineWidth(1/scale)
20 |
21 | ctx.setSourceRGBA(1, 1, 1, 1)
22 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
23 | ctx.fill()
24 |
25 | type Body = object
26 | id: int
27 | pos: Vec3
28 | vel: Vec3
29 | mass: float
30 | var bodies: seq[Body]
31 |
32 | # sun
33 | var body = Body()
34 | body.id = 0
35 | bodies.add body
36 |
37 | downloadSpk("de435.bsp")
38 | ctx.setSourceRGBA(1, 1, 1, 0.15)
39 | var spkFile = readSpk("de435.bsp")
40 | for planet in simpleElements:
41 | var step = planet.period / 360
42 | for i in 0..360:
43 | let time = step * float(i)
44 | let pos = spkFile.posAt(time, planet.id, 0) / AU
45 | ctx.lineTo(pos.x, pos.y)
46 | ctx.closePath()
47 | ctx.stroke()
48 |
49 | var body = Body()
50 | body.id = planet.id
51 | bodies.add body
52 |
53 | const HOUR = 60 * 60
54 | const MINUTE = 60
55 | bodies[0].mass = 1.98847E+30
56 | bodies[1].mass = 3.30E+23
57 | bodies[2].mass = 4.87E+24
58 | bodies[3].mass = 5.97E+24
59 | bodies[4].mass = 6.42E+23
60 | bodies[5].mass = 1.90E+27
61 | bodies[6].mass = 5.68E+26
62 | bodies[7].mass = 8.68E+25
63 | bodies[8].mass = 1.02E+26
64 | bodies[9].mass = 1.46E+22
65 |
66 |
67 |
68 |
69 | proc minDistance(startTime: float, kick: Vec3, stepTime: float, draw: bool): (float, float) =
70 |
71 |
72 |
73 | for planet in simpleElements:
74 | bodies[planet.id].pos = spkFile.posAt(startTime, planet.id, 0)
75 | bodies[planet.id].vel = spkFile.velAt(startTime, planet.id, 0)
76 |
77 | var spaceCraft = Body()
78 | spaceCraft.pos = bodies[3].pos
79 | var prevPos = spaceCraft.pos
80 | spaceCraft.vel = bodies[3].vel + kick
81 | spaceCraft.mass = 110 * 1000 # The Martian: Hermes mass: 110 tons
82 |
83 | if draw:
84 | ctx.setSourceRGBA(1, 1, 1, 1)
85 | ctx.arc(spaceCraft.pos.x/AU, spaceCraft.pos.y/AU, 3/scale, 0.0, 2.0*PI)
86 | ctx.fill()
87 |
88 | var minMarsDist = 1E100
89 |
90 |
91 | let maxStep = int(JULIAN_YEAR*2/stepTime)
92 | #let stepTime = 100000.0
93 | var time = startTime
94 | var minTime = 0.0
95 | var minPos = vec3(0,0,0)
96 | for step in 0..maxStep:
97 |
98 | spaceCraft.pos = spaceCraft.pos + spaceCraft.vel * stepTime
99 |
100 | for other in bodies[0..0]:
101 | #if other.id != 0:
102 | # other.pos = spkFile.posAt(time, other.id, 0)
103 | let offset = other.pos - spaceCraft.pos
104 | let dv = G * other.mass / pow(offset.length, 2)
105 | let direction = offset.normalize()
106 | spaceCraft.vel = spaceCraft.vel + direction * dv * stepTime
107 |
108 | let
109 | marsPos = spkFile.posAt(time, 4, 0)
110 | marsVel = spkFile.velAt(time, 4, 0)
111 | let (minMars, minSpaceCraftPos, dist) = computeSegToSeg(marsPos, marsPos + marsVel, spaceCraft.pos, prevPos)
112 | #let dist = distPToS(, spaceCraft.pos, prevPos)
113 |
114 | #let dist = (marsPos - spaceCraft.pos).length
115 | #print dist, "vs", dist2, abs(dist - dist2) / dist2
116 |
117 | if minMarsDist > dist:
118 | minMarsDist = dist
119 | minTime = time
120 | minPos = minSpaceCraftPos #pointPToS(spkFile.posAt(time, 4, 0), spaceCraft.pos, prevPos)
121 |
122 |
123 | prevPos = spaceCraft.pos
124 | time += stepTime
125 |
126 | if draw:
127 | #echo dist
128 | #echo spaceCraft.pos
129 | ctx.lineTo(spaceCraft.pos.x/AU, spaceCraft.pos.y/AU)
130 |
131 | if draw:
132 | ctx.stroke()
133 |
134 | let marsPos = spkFile.posAt(minTime, 4, 0)
135 | ctx.setSourceRGBA(1, 0, 0, 1)
136 | ctx.arc(marsPos.x/AU, marsPos.y/AU, 5/scale, 0.0, 2.0*PI)
137 | ctx.fill()
138 |
139 | ctx.setSourceRGBA(1, 1, 1, 1)
140 | ctx.arc(minPos.x/AU, minPos.y/AU, 3/scale, 0.0, 2.0*PI)
141 | ctx.fill()
142 |
143 | return (minMarsDist, minTime)
144 |
145 |
146 |
147 | ctx.setSourceRGBA(1,1,1,1)
148 |
149 | var
150 | bestTime = 1552696987.0
151 | bestKick = vec3(0, 0, 0)
152 | bestMinDist = 1E30
153 | curTime = bestTime
154 | curKick = bestKick
155 | curMinDist = 1E30
156 |
157 |
158 |
159 |
160 | # let dayScan = 365
161 |
162 | # let startTime = 1552715293.0
163 | # randomize()
164 | # var minTime: float
165 |
166 | # bestKick = vec3(0, 0, 0)
167 | # bestMinDist = 1E30
168 | # for i in 0..dayScan:
169 | # curTime = startTime + float(i) * DAY*2
170 |
171 | # let dv = 3700.0
172 | # curKick = spkFile.velAt(curTime, 3, 0).normalize() * dv
173 | # (curMinDist, minTime) = minDistance(curTime, curKick, 10000, false)
174 |
175 | # if curMinDist < bestMinDist:
176 | # bestMinDist = curMinDist
177 | # bestTime = curTime
178 | # bestKick = curKick
179 | # let dv = bestKick.length()
180 | # print "best scan day: ", bestMinDist, bestTime, dv, bestKick
181 |
182 |
183 | bestMinDist=3670923806.137688
184 | bestTime=1594878493.0
185 | bestKick=vec3(3383.63238149, 1497.00765049, 0.03649411)
186 |
187 |
188 | # var
189 | # vecTime: float
190 | # vecKick: Vec3
191 | # for accuracy in [20000]:
192 | # print accuracy
193 | # let maxSearch = accuracy
194 | # var a = 1.0
195 | # var g = 0.10
196 | # for i in 0..maxSearch:
197 | # # random search
198 | # let
199 | # vecTimeD = DAY * rand(-1.0..1.0)
200 | # vecKickD = vec3(rand(-1.0..1.0), rand(-1.0..1.0), rand(-0.1..0.1)) * rand(0.0..100.0)
201 |
202 | # vecTime = vecTime * (1-a) + vecTimeD * a
203 | # vecKick = vecKick * (1-a) + vecKickD * a
204 |
205 | # curTime = bestTime + vecTime * g
206 | # curKick = bestKick + vecKick * g
207 |
208 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
209 |
210 | # if curMinDist < bestMinDist:
211 | # bestMinDist = curMinDist
212 | # bestTime = curTime
213 | # bestKick = curKick
214 |
215 | # vecTime *= -0.2
216 | # vecKick *= -0.2
217 |
218 | # a = 0.0
219 | # let dv = bestKick.length()
220 | # print "best search: ", accuracy, i, g, bestMinDist, bestTime, dv, bestKick
221 |
222 | # a = min(1.0, a + 0.01)
223 |
224 |
225 |
226 |
227 |
228 | # var
229 | # adj: array[4, float]
230 |
231 | # let accuracy = 20000
232 |
233 | # for i in 0..1000:
234 | # adj[0] = 0
235 | # adj[1] = 0
236 | # adj[2] = 0
237 | # adj[3] = 0
238 |
239 | # for pick in 0..<4:
240 | # adj[pick] = rand(-1.0 .. +1.0)
241 |
242 | # curTime = bestTime + adj[0]
243 | # curKick.x = bestKick.x + adj[1]
244 | # curKick.y = bestKick.y + adj[2]
245 | # curKick.z = bestKick.z + adj[3]
246 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
247 |
248 | # if curMinDist < bestMinDist:
249 | # print "adjusting +1", pick
250 |
251 | # while curMinDist < bestMinDist:
252 | # bestMinDist = curMinDist
253 | # bestTime = curTime
254 | # bestKick = curKick
255 | # let dv = bestKick.length()
256 | # print " best search: ", accuracy, bestMinDist, bestTime, dv, bestKick
257 |
258 | # adj[0] *= 1.2
259 | # adj[1] *= 1.2
260 | # adj[2] *= 1.2
261 | # adj[3] *= 1.2
262 |
263 | # curTime = bestTime + adj[0]
264 | # curKick.x = bestKick.x + adj[1]
265 | # curKick.y = bestKick.y + adj[2]
266 | # curKick.z = bestKick.z + adj[3]
267 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
268 |
269 | # adj[0] *= 0.5
270 | # adj[1] *= 0.5
271 | # adj[2] *= 0.5
272 | # adj[3] *= 0.5
273 | # print "end"
274 |
275 | # # flip
276 | # adj[0] *= -1
277 | # adj[1] *= -1
278 | # adj[2] *= -1
279 | # adj[3] *= -1
280 |
281 | # curTime = bestTime + adj[0]
282 | # curKick.x = bestKick.x + adj[1]
283 | # curKick.y = bestKick.y + adj[2]
284 | # curKick.z = bestKick.z + adj[3]
285 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
286 |
287 | # if curMinDist < bestMinDist:
288 | # print "adjusting -1", pick
289 |
290 | # while curMinDist < bestMinDist:
291 | # bestMinDist = curMinDist
292 | # bestTime = curTime
293 | # bestKick = curKick
294 | # let dv = bestKick.length()
295 | # print " best search: ", accuracy, bestMinDist, bestTime, dv, bestKick
296 |
297 | # adj[0] *= 1.2
298 | # adj[1] *= 1.2
299 | # adj[2] *= 1.2
300 | # adj[3] *= 1.2
301 |
302 | # curTime = bestTime + adj[0]
303 | # curKick.x = bestKick.x + adj[1]
304 | # curKick.y = bestKick.y + adj[2]
305 | # curKick.z = bestKick.z + adj[3]
306 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
307 |
308 | # print "end"
309 |
310 |
311 |
312 |
313 | # let accuracy = 10000
314 | # print accuracy
315 | # let maxSearch = 1000
316 | # var a = 1.0
317 |
318 | # for i in 0..maxSearch:
319 | # # random search
320 | # let
321 | # #vecTime = DAY * rand(-1.0..1.0)
322 | # vecKick = vec3(rand(-1.0..1.0), rand(-1.0..1.0), rand(-0.1..0.1)) * rand(0.0..10.0)
323 |
324 | # let a = 1.0 - pow(i/maxSearch, 1)
325 |
326 | # #curTime = bestTime + vecTime * a
327 | # curKick = bestKick + vecKick * a
328 |
329 | # (curMinDist, minTime) = minDistance(curTime, curKick, float(accuracy), false)
330 |
331 | # if curMinDist < bestMinDist:
332 | # bestMinDist = curMinDist
333 | # bestTime = curTime
334 | # bestKick = curKick
335 | # let dv = bestKick.length()
336 | # print "best search: ", accuracy, bestMinDist, bestTime, dv, bestKick, i, a
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 | # best vector search
346 | for accuracy in [10000, 1000, 100, 10, 1]:
347 | bestMinDist = minDistance(bestTime, bestKick, float(accuracy), false)[0]
348 | print accuracy, bestMinDist
349 |
350 | for k in 0..1000:
351 | let a = 100 / pow(float(k+1), 2)
352 | if a < 10: break
353 |
354 | for i in 0..100:
355 | let adjs = @[
356 | vec3( 1, 0, 0) * a,
357 | vec3( 0, 1, 0) * a,
358 | vec3( 0, 0, 1) * a,
359 | vec3(-1, 0, 0) * a,
360 | vec3( 0, -1, 0) * a,
361 | vec3( 0, 0, -1) * a
362 | ]
363 | var dists = @[0.0,0,0,0,0,0]
364 |
365 | for i in 0..<6:
366 | dists[i] = minDistance(bestTime, bestKick + adjs[i], float(accuracy), false)[0]
367 | #print dists[i], "vs", bestMinDist
368 |
369 | var
370 | minDist = bestMinDist
371 | minI = -1
372 | for i in 0..<6:
373 | if dists[i] < minDist:
374 | minI = i
375 | minDist = dists[i]
376 | if minI == -1:
377 | echo "<=="
378 | break
379 |
380 | bestKick = bestKick + adjs[minI]
381 | bestMinDist = dists[minI]
382 | echo "better!", bestMinDist, adjs[minI]
383 |
384 |
385 |
386 |
387 | discard minDistance(bestTime, bestKick, 10000, true)
388 |
389 | discard surface.writeToPng("tests/earthToMarsSim.png")
390 |
391 |
--------------------------------------------------------------------------------
/tests/earthToMarsSim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/earthToMarsSim.png
--------------------------------------------------------------------------------
/tests/generateOrbitalElements.nim:
--------------------------------------------------------------------------------
1 | import strformat
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64
4 |
5 | var
6 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
7 | ctx = surface.create()
8 |
9 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
10 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
11 | ctx.fill()
12 |
13 | let scale = 10.0
14 |
15 | ctx.translate(500, 500)
16 | ctx.scale(scale, scale)
17 | ctx.setLineWidth(1/scale)
18 |
19 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
20 | ctx.fill()
21 |
22 | var hz = newHorizonClient()
23 | import json
24 | ctx.setSourceRGBA(1, 1, 1, 1)
25 | var newElements = newSeq[OrbitalElements]()
26 | for planet in simpleElements:
27 | let elements = hz.getOrbitalElements(
28 | 0.0, planet.id, 0)
29 | echo planet.name
30 | var planet2 = elements
31 | planet2.id = planet.id
32 | planet2.name = planet.name
33 | newElements.add(planet2)
34 | var step = planet2.period / 360
35 | for i in 0..360:
36 | let time = step * float(i)
37 | let pos = planet2.posAt(time)
38 | ctx.lineTo(pos.x, pos.y)
39 | ctx.stroke()
40 |
41 | echo $(%newElements)
42 |
43 | hz.close()
44 |
45 | discard surface.writeToPng("tests/generatedOE.png")
--------------------------------------------------------------------------------
/tests/generateStats.nim:
--------------------------------------------------------------------------------
1 | import strformat
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import orbits/vmath64
4 |
5 | var hz = newHorizonClient()
6 |
7 | # mercury 58.6462 days (sidereal)
8 | # venus 243.018 days (sidereal, retrograde)
9 | # earth 0.99726968 days
10 | # mars 1.02595676 days
11 | # jupiter 0.41354 days
12 |
13 |
14 | # mercury 0.01
15 | # venus 177.36
16 | # earth 23.45
17 | # mars 25.19
18 | # jupiter 3.13
19 | # saturn 26.73
20 | # uranus 97.77
21 | # neptue 28.32
22 | # pluto 122.53
23 |
24 |
25 | hz.debug = false
26 |
27 | for planetId in [199, 299, 399, 499, 599, 699, 799, 899, 999]:
28 | echo planetId
29 | echo " radius:", hz.getRadius(Y2000, planetId), " m"
30 | echo " axis:", hz.getRotationAxis(Y2000, planetId), " unit vector ", hz.getRotationAxis(Y2000, planetId).angleBetween(vec3(0,0,1))/PI*180, " deg"
31 | var speed = hz.getRotationAngularSpeed(Y2000, planetId)
32 | echo " angular speed:", speed, " rad/s ", (2*PI/speed)/DAY, " days"
33 |
34 |
35 | hz.close()
36 |
--------------------------------------------------------------------------------
/tests/generatedOE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/generatedOE.png
--------------------------------------------------------------------------------
/tests/matching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/matching.png
--------------------------------------------------------------------------------
/tests/merkuryPrecession.nim:
--------------------------------------------------------------------------------
1 | # import orbits/simple, orbits/complex
2 | # import cairo, math
3 |
4 | # var
5 | # surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
6 | # ctx = surface.create()
7 |
8 | # ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
9 | # ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
10 | # ctx.fill()
11 |
12 | # ctx.translate(500, 500)
13 | # ctx.scale(1000, 1000)
14 |
15 | # ctx.setSourceRGBA(1, 1, 1, 1)
16 | # ctx.setLineWidth(0.001)
17 |
18 | # # draw orbit of merkury 10 times showing shift over 500y
19 | # let shiftBy = 500.0 * (60*60*24*365)
20 | # for j in 0..10:
21 | # ctx.setSourceRGBA(1, 1, 1, float(j) / 10 * 0.8 + 0.2)
22 | # let planet = complexElements[0] # merkury
23 | # var step = planet.period / 360
24 | # for i in 0..360:
25 | # let pos = planet.posAt(step * float(i) + float(j) * shiftBy)
26 | # ctx.lineTo(pos.x, pos.y)
27 | # ctx.stroke()
28 |
29 | # ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
30 | # ctx.fill()
31 |
32 | # discard surface.writeToPng("tests/merkuryPrecession.png")
33 |
34 |
35 | import orbits/simple, orbits/spk
36 | import cairo, orbits/vmath64
37 |
38 | var
39 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
40 | ctx = surface.create()
41 |
42 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
43 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
44 | ctx.fill()
45 |
46 | ctx.translate(500, 500)
47 | ctx.scale(1000, 1000)
48 |
49 | ctx.setSourceRGBA(1, 1, 1, 1)
50 | ctx.setLineWidth(0.001)
51 |
52 | ctx.setSourceRGBA(1, 1, 1, 1)
53 | var spkFile = readSpk("de435.bsp")
54 |
55 | # draw orbit of merkury 5 times showing shift over 10 earth years
56 | let shiftBy = 10.0 * (60*60*24*365)
57 | for j in 0..5:
58 | ctx.setSourceRGBA(1, 1, 1, float(j) / 5 * 0.8 + 0.2)
59 | let planetId = 1 # merkury
60 | var step = simpleElements[0].period / 360
61 | for i in 0..360:
62 | let time = step * float(i) + shiftBy * float(j)
63 | let pos = spkFile.posAt(time, planetId, 0) / AU
64 | ctx.lineTo(pos.x, pos.y)
65 | ctx.stroke()
66 |
67 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
68 | ctx.fill()
69 |
70 | discard surface.writeToPng("tests/merkuryPrecession.png")
--------------------------------------------------------------------------------
/tests/merkuryPrecession.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/merkuryPrecession.png
--------------------------------------------------------------------------------
/tests/milkyway.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/milkyway.png
--------------------------------------------------------------------------------
/tests/nim.cfg:
--------------------------------------------------------------------------------
1 | --path:"../src/"
2 |
--------------------------------------------------------------------------------
/tests/orbitsHorizon.nim:
--------------------------------------------------------------------------------
1 | import strformat
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64
4 |
5 | var
6 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
7 | ctx = surface.create()
8 |
9 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
10 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
11 | ctx.fill()
12 |
13 | let scale = 10.0
14 |
15 | ctx.translate(500, 500)
16 | ctx.scale(scale, scale)
17 | ctx.setLineWidth(1/scale)
18 |
19 | ctx.setSourceRGBA(1, 1, 1, 1)
20 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
21 | ctx.fill()
22 |
23 | var hz = newHorizonClient()
24 |
25 | # simple orbits in red
26 | ctx.setSourceRGBA(1, 0, 0, 1)
27 | for planet in simpleElements:
28 | var step = planet.period / 360
29 | for i in 0..360:
30 | let time = step * float(i)
31 | let pos = planet.posAt(time) / AU
32 | ctx.lineTo(pos.x, pos.y)
33 | ctx.closePath()
34 | ctx.stroke()
35 |
36 | #spk orbits in blue
37 | downloadSpk("de435.bsp")
38 | ctx.setSourceRGBA(0, 1, 0, 1)
39 | var spkFile = readSpk("de435.bsp")
40 | for planet in simpleElements:
41 | var step = planet.period / 360
42 | for i in 0..360:
43 | let time = step * float(i)
44 | let pos = spkFile.posAt(time, planet.id, 0) / AU
45 | ctx.lineTo(pos.x, pos.y)
46 | ctx.closePath()
47 | ctx.stroke()
48 |
49 | #horizon orbits in white
50 | ctx.setSourceRGBA(1, 1, 1, 1)
51 | for planet in simpleElements:
52 | let entries = hz.getOrbitalVectorsSeq(
53 | fromTime = 0.0,
54 | toTime = planet.period,
55 | steps = 360,
56 | targetId = planet.id,
57 | observerId = 0)
58 | for entry in entries:
59 | let pos = entry.pos / AU
60 | ctx.lineTo(pos.x, pos.y)
61 | ctx.closePath()
62 | ctx.stroke()
63 | hz.close()
64 |
65 | discard surface.writeToPng("tests/orbitsHorizon.png")
--------------------------------------------------------------------------------
/tests/orbitsHorizon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/orbitsHorizon.png
--------------------------------------------------------------------------------
/tests/orbitsSimple.nim:
--------------------------------------------------------------------------------
1 | import orbits/simple, orbits/vmath64, cairo
2 |
3 | var
4 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
5 | ctx = surface.create()
6 |
7 | ctx.setSourceRGB(0.11, 0.14, 0.42)
8 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
9 | ctx.fill()
10 |
11 |
12 | let scale = 10.0
13 |
14 | ctx.translate(500, 500)
15 | ctx.scale(scale, scale)
16 | ctx.setLineWidth(1/scale)
17 |
18 | ctx.setSourceRGBA(1, 1, 1, 1)
19 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
20 | ctx.fill()
21 |
22 |
23 | for planet in simpleElements:
24 | var step = planet.period / 360
25 | for i in 0..360:
26 | let pos = planet.posAt(step * float(i)) / AU
27 | ctx.lineTo(pos.x, pos.y)
28 | ctx.stroke()
29 |
30 | discard surface.writeToPng("tests/orbitsSimple.png")
--------------------------------------------------------------------------------
/tests/orbitsSimple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/orbitsSimple.png
--------------------------------------------------------------------------------
/tests/orbitsSimulated.nim:
--------------------------------------------------------------------------------
1 | import strformat
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64
4 |
5 | var
6 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
7 | ctx = surface.create()
8 |
9 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
10 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
11 | ctx.fill()
12 |
13 | let scale = 250.0
14 |
15 | ctx.translate(500, 500)
16 | ctx.scale(scale, scale)
17 | ctx.setLineWidth(1/scale)
18 |
19 | ctx.setSourceRGBA(1, 1, 1, 1)
20 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
21 | ctx.fill()
22 |
23 | var hz = newHorizonClient()
24 |
25 | type Body = object
26 | id: int
27 | pos: Vec3
28 | vel: Vec3
29 | mass: float
30 | var bodies: seq[Body]
31 |
32 | # sun
33 | var body = Body()
34 | body.id = 0
35 | bodies.add body
36 |
37 | # get inital obits from horizon
38 | ctx.setSourceRGBA(1, 1, 1, 0.1)
39 | for planet in simpleElements:
40 | let entries = hz.getOrbitalVectorsSeq(
41 | fromTime = 0.0,
42 | toTime = planet.period,
43 | steps = 360,
44 | targetId = planet.id,
45 | observerId = 0)
46 | var body = Body()
47 | body.id = planet.id
48 | body.pos = entries[0].pos
49 | body.vel = entries[0].vel
50 | bodies.add body
51 |
52 | for entry in entries:
53 | let pos = entry.pos / AU
54 | ctx.lineTo(pos.x, pos.y)
55 | ctx.closePath()
56 | ctx.stroke()
57 |
58 | const G = 6.674E-11
59 | const HOUR = 60 * 60
60 | const MINUTE = 60
61 | bodies[0].mass = 1.98847E+30
62 | bodies[1].mass = 3.30E+23
63 | bodies[2].mass = 4.87E+24
64 | bodies[3].mass = 5.97E+24
65 | bodies[4].mass = 6.42E+23
66 | bodies[5].mass = 1.90E+27
67 | bodies[6].mass = 5.68E+26
68 | bodies[7].mass = 8.68E+25
69 | bodies[8].mass = 1.02E+26
70 | bodies[9].mass = 1.46E+22
71 |
72 | let maxStep = 1000000
73 | let stepTime = 10.0
74 | for step in 0..maxStep:
75 |
76 | for body in bodies.mitems:
77 | body.pos = body.pos + body.vel * stepTime
78 |
79 | for other in bodies:
80 | if body.id == other.id: continue
81 | let offset = other.pos - body.pos
82 | let dv = G * other.mass / pow(offset.length, 2)
83 | let direction = offset.normalize()
84 | body.vel = body.vel + direction * dv * stepTime
85 |
86 |
87 | if step mod (maxStep div 50) == 0:
88 |
89 | for body in bodies:
90 | ctx.setSourceRGBA(1, 1, 1, step/maxStep + 0.1)
91 | ctx.arc(body.pos.x/AU, body.pos.y/AU, 3/scale, 0.0, 2.0*PI)
92 | ctx.fill()
93 |
94 |
95 | hz.close()
96 |
97 |
98 | discard surface.writeToPng("tests/orbitsSimulated.png")
--------------------------------------------------------------------------------
/tests/orbitsSimulated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/orbitsSimulated.png
--------------------------------------------------------------------------------
/tests/orbitsSpk.nim:
--------------------------------------------------------------------------------
1 | import orbits/simple, orbits/spk
2 | import cairo, orbits/vmath64
3 |
4 | var
5 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
6 | ctx = surface.create()
7 |
8 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
9 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
10 | ctx.fill()
11 |
12 | let scale = 10.0
13 |
14 | ctx.translate(500, 500)
15 | ctx.scale(scale, scale)
16 | ctx.setLineWidth(1/scale)
17 |
18 | ctx.setSourceRGBA(1, 0, 0, 1)
19 | for planet in simpleElements:
20 | var step = planet.period / 360
21 | for i in 0..360:
22 | let time = step * float(i)
23 | let pos = planet.posAt(time) / AU
24 | ctx.lineTo(pos.x, pos.y)
25 | ctx.stroke()
26 |
27 | ctx.setSourceRGBA(1, 1, 1, 1)
28 |
29 | downloadSpk("de435.bsp")
30 |
31 | var spkFile = readSpk("de435.bsp")
32 | for planet in simpleElements:
33 | var step = planet.period / 360
34 | for i in 0..360:
35 | let time = step * float(i)
36 | let pos = spkFile.posAt(time, planet.id, 0) / AU
37 | ctx.lineTo(pos.x, pos.y)
38 | ctx.stroke()
39 |
40 |
41 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
42 | ctx.fill()
43 |
44 | discard surface.writeToPng("tests/orbitsSpk.png")
--------------------------------------------------------------------------------
/tests/orbitsSpk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/orbitsSpk.png
--------------------------------------------------------------------------------
/tests/solarsystemSpk.nim:
--------------------------------------------------------------------------------
1 | import strformat, times
2 | import orbits/simple, orbits/spk#, orbits/vmath64
3 | import pixie/demo, vmath
4 |
5 | let now = epochTime()
6 |
7 |
8 | downloadSpk("de435.bsp")
9 |
10 | var spkFile = readSpk("de435.bsp")
11 |
12 | var currentTime = 0.0
13 |
14 | start("Solar System from NASA's SPK files that use Chebyshev polynomials")
15 | while true:
16 | screen.fill(color(0.11, 0.14, 0.42, 1.0))
17 |
18 | let scaleK = 10.0
19 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
20 |
21 | var path: Path
22 | path.circle(vec2(0, 0), 0.1)
23 | screen.fillPath(path, color(1,1,1,1), mat)
24 |
25 | for planet in simpleElements:
26 | var step = planet.period / 360
27 | var path: Path
28 |
29 | for i in 0..360:
30 | let time = step * float(i)
31 | let pos = spkFile.posAt(time, planet.id, 0) / AU
32 | if i == 0:
33 | path.moveTo(pos.x.float32, pos.y.float32)
34 | else:
35 | path.lineTo(pos.x.float32, pos.y.float32)
36 | path.closePath()
37 |
38 |
39 | var currentPos = spkFile.posAt(currentTime, planet.id, 0) / AU
40 | var pathBody: Path
41 | pathBody.circle(vec3(currentPos).xy, 0.1)
42 | screen.fillPath(pathBody, color(1,1,1,1), mat)
43 |
44 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 0.1)
45 |
46 | tick()
47 |
48 | currentTime += 60*60*25 * 7 # week per frame
49 |
--------------------------------------------------------------------------------
/tests/solvertest.nim:
--------------------------------------------------------------------------------
1 | import orbits/solvers, print
2 |
3 |
4 | print MACHINE_EPSILON
5 |
6 | block:
7 | func f1(x: float): float = x * x + x - 1
8 | for i in 0 .. 10:
9 | let x = float(i)/10.0
10 | print x, f1(x)
11 | echo goldenSectionSearch(0, 1, f1)
12 |
13 | func f2(x: float): float = - x * x + x
14 | for i in 0 .. 10:
15 | let x = float(i)/10.0
16 | print x, f2(x)
17 | echo goldenSectionSearch(0.1, 1, f2)
18 |
19 |
20 | func f3(x: float): float = x * x - x
21 | for i in 0 .. 10:
22 | let x = float(i)/10.0
23 | print x, f3(x)
24 | echo goldenSectionSearch(0.0, 1, f3)
25 |
26 | block:
27 | proc f1(x: float): float = x * x + x
28 | proc f2(x: float): float = 2 * x + 1
29 | for i in 0 .. 10:
30 | let x = float(i)/10.0
31 | print x, f1(x), f2(x)
32 | echo newtonsRoot(0.1, f1, f2)
--------------------------------------------------------------------------------
/tests/spacecraft.nim:
--------------------------------------------------------------------------------
1 | import strformat, times
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64
4 |
5 | var
6 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
7 | ctx = surface.create()
8 |
9 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
10 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
11 | ctx.fill()
12 |
13 | let scale = 3.5
14 |
15 | ctx.translate(500, 500)
16 | ctx.scale(scale, scale)
17 |
18 |
19 | ctx.selectFontFace("Sans", FONT_SLANT_NORMAL, FONT_WEIGHT_NORMAL)
20 | ctx.setFontSize(12.0/scale)
21 |
22 | ctx.setSourceRGBA(1, 1, 1, 1)
23 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
24 | ctx.fill()
25 |
26 | var hz = newHorizonClient()
27 |
28 | # simple orbits in red
29 | ctx.setSourceRGBA(1, 1, 1, 0.1)
30 | ctx.setLineWidth(2/scale)
31 | for planet in simpleElements:
32 | var step = planet.period / 360
33 | for i in 0..360:
34 | let time = step * float(i)
35 | let pos = planet.posAt(time) / AU
36 | ctx.lineTo(pos.x, pos.y)
37 | ctx.closePath()
38 | ctx.stroke()
39 |
40 | ctx.setLineWidth(1/scale)
41 |
42 | block:
43 | #plot voyager1
44 | ctx.setSourceRGBA(1, 1, 1, 1)
45 | let id = -31
46 | let entries = hz.getOrbitalVectorsSeq(
47 | 242290800+DAY,
48 | 1552284021,
49 | 1000,
50 | id,
51 | 0)
52 | ctx.newPath()
53 | for entry in entries:
54 | let pos = entry.pos / AU
55 | ctx.lineTo(pos.x, pos.y)
56 | ctx.stroke()
57 |
58 | let pos = entries[^1].pos / AU
59 | ctx.lineTo(pos.x, pos.y)
60 | ctx.showText(" Voyager 1")
61 |
62 |
63 | block:
64 | #plot voyager2
65 | ctx.setSourceRGBA(1, 1, 1, 1)
66 | let id = -32
67 | let entries = hz.getOrbitalVectorsSeq(
68 | 240908400+DAY,
69 | 1552284021,
70 | 1000,
71 | id,
72 | 0)
73 | ctx.newPath()
74 | for entry in entries:
75 | let pos = entry.pos / AU
76 | ctx.lineTo(pos.x, pos.y)
77 | ctx.stroke()
78 |
79 | let pos = entries[^1].pos / AU
80 | ctx.lineTo(pos.x, pos.y)
81 | ctx.showText(" Voyager 2")
82 |
83 | block:
84 | #plot new horizons
85 | ctx.setSourceRGBA(1, 1, 1, 1)
86 | let id = -98
87 | let entries = hz.getOrbitalVectorsSeq(
88 | 1137657600+DAY,
89 | 1552284021,
90 | 1000,
91 | id,
92 | 0)
93 | ctx.newPath()
94 | for entry in entries:
95 | let pos = entry.pos / AU
96 | ctx.lineTo(pos.x, pos.y)
97 | ctx.stroke()
98 |
99 | let pos = entries[^1].pos / AU
100 | ctx.lineTo(pos.x, pos.y)
101 | ctx.showText(" New Horizon")
102 |
103 |
104 | # block:
105 | # #plot Dawn
106 | # ctx.setSourceRGBA(1, 1, 1, 1)
107 | # let voyager2Id = -203
108 | # let entries = hz.getOrbitalVectorsSeq(
109 | # 1190876400 + DAY,
110 | # 1552284021,
111 | # 1000,
112 | # voyager2Id,
113 | # 0)
114 | # ctx.newPath()
115 | # for entry in entries:
116 | # let pos = entry.pos / AU
117 | # ctx.lineTo(pos.x, pos.y)
118 | # ctx.stroke()
119 |
120 | # let pos = entries[^1].pos / AU
121 | # ctx.lineTo(pos.x, pos.y)
122 | # ctx.showText(" Dawn")
123 |
124 |
125 | hz.close()
126 |
127 | discard surface.writeToPng("tests/spacecraft.png")
--------------------------------------------------------------------------------
/tests/spacecraft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/spacecraft.png
--------------------------------------------------------------------------------
/tests/starman.nim:
--------------------------------------------------------------------------------
1 | import strformat, times
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import cairo, orbits/vmath64
4 |
5 | let now = epochTime()
6 |
7 | var
8 | surface = imageSurfaceCreate(FORMAT_ARGB32, 1000, 1000)
9 | ctx = surface.create()
10 |
11 | ctx.setSourceRGBA(0.11, 0.14, 0.42, 1.0)
12 | ctx.rectangle(0, 0, float surface.getWidth, float surface.getHeight)
13 | ctx.fill()
14 |
15 | let scale = 250.0
16 |
17 | ctx.translate(500, 500)
18 | ctx.scale(scale, scale)
19 |
20 |
21 | ctx.selectFontFace("Sans", FONT_SLANT_NORMAL, FONT_WEIGHT_NORMAL)
22 | ctx.setFontSize(12.0/scale)
23 |
24 | ctx.setSourceRGBA(1, 1, 1, 1)
25 | ctx.arc(0, 0, 0.00464, 0.0, 2.0*PI)
26 | ctx.fill()
27 |
28 | var hz = newHorizonClient()
29 |
30 | # simple orbits in red
31 | ctx.setSourceRGBA(1, 1, 1, 0.1)
32 | ctx.setLineWidth(2/scale)
33 | for planet in simpleElements:
34 | var step = planet.period / 360
35 | for i in 0..360:
36 | let time = step * float(i)
37 | let pos = planet.posAt(time) / AU
38 | ctx.lineTo(pos.x, pos.y)
39 | ctx.closePath()
40 | ctx.stroke()
41 |
42 | ctx.setLineWidth(1/scale)
43 |
44 |
45 | block:
46 | ctx.setSourceRGBA(1, 1, 1, 1)
47 | let id = -143205
48 | let entries = hz.getOrbitalVectorsSeq(
49 | 1517904000+DAY,
50 | now,
51 | 1000,
52 | id,
53 | 0)
54 | ctx.newPath()
55 | for entry in entries:
56 | let pos = entry.pos / AU
57 | ctx.lineTo(pos.x, pos.y)
58 | ctx.stroke()
59 |
60 | let pos = entries[^1].pos / AU
61 | ctx.lineTo(pos.x, pos.y)
62 |
63 |
64 | echo entries[^1].pos.length / AU
65 | echo entries[^1].vel.length
66 |
67 | let earthOv = hz.getOrbitalVectors(
68 | now,
69 | 399,
70 | 0)
71 |
72 | let
73 | vel = int(entries[^1].vel.length)
74 | dist = int((earthOv.pos - entries[^1].pos).length / 1000 - 6378.1)
75 |
76 | ctx.save()
77 | ctx.moveTo(pos.x, pos.y)
78 | ctx.showText(" Starman")
79 | ctx.restore()
80 | ctx.save()
81 | ctx.moveTo(pos.x, pos.y+20/scale)
82 | ctx.showText(&" {$vel}m/s")
83 | ctx.restore()
84 | ctx.save()
85 | ctx.moveTo(pos.x, pos.y+40/scale)
86 | ctx.showText(&" {$dist}km")
87 | ctx.restore()
88 |
89 | hz.close()
90 |
91 | discard surface.writeToPng("tests/starman.png")
--------------------------------------------------------------------------------
/tests/starman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/treeform/orbits/b53ebe666d9f881fee26fd42bbfba5f17a3b0400/tests/starman.png
--------------------------------------------------------------------------------
/tests/starman2.nim:
--------------------------------------------------------------------------------
1 | import strformat, times
2 | import orbits/simple, orbits/spk, orbits/horizon
3 | import pixie/demo, vmath
4 |
5 | downloadSpk("de435.bsp")
6 |
7 | var spkFile = readSpk("de435.bsp")
8 |
9 | var fromTime = 1517904000.float64 + DAY # starman launch date
10 | let toTime = epochTime()
11 | let id = -143205 # starman ID
12 |
13 | var currentTime = fromTime
14 |
15 | var hz = newHorizonClient()
16 | let hzStarManVecs = hz.getOrbitalVectorsSeq(
17 | fromTime,
18 | toTime,
19 | 1000,
20 | id,
21 | 0
22 | )
23 |
24 | proc computePos(entries: seq[OrbitalVectors], time: float64): DVec3 =
25 | for i, entry in entries:
26 | if entry.time < time:
27 | continue
28 | assert entries[i-1].time < time and entries[i].time > time
29 | let
30 | a = entries[i-1]
31 | b = entries[i]
32 | timeDelta = a.time - b.time
33 | return lerp(b.pos, a.pos, (time - a.time)/timeDelta)
34 |
35 | start("Where is SpaceX's Starman?")
36 | while true:
37 | screen.fill(color(0.11, 0.14, 0.42, 1.0))
38 |
39 | let scaleK = 100.0
40 | var mat = translate(vec2(400, 300)) * scale(vec2(scaleK, scaleK))
41 |
42 | var path: Path
43 | path.circle(vec2(0, 0), 0.1)
44 | screen.fillPath(path, color(1,1,1,1), mat)
45 |
46 | for planet in simpleElements:
47 | var step = planet.period / 360
48 | var path: Path
49 |
50 | for i in 0..360:
51 | let time = step * float(i)
52 | let pos = spkFile.posAt(time, planet.id, 0) / AU
53 | if i == 0:
54 | path.moveTo(pos.x.float32, pos.y.float32)
55 | else:
56 | path.lineTo(pos.x.float32, pos.y.float32)
57 | path.closePath()
58 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 0.01)
59 |
60 | var currentPos = spkFile.posAt(currentTime, planet.id, 0) / AU
61 | var pathBody: Path
62 | pathBody.circle(vec3(currentPos).xy, 0.01)
63 | screen.fillPath(pathBody, color(1,1,1,1), mat)
64 |
65 | # drawing the starman
66 | block:
67 | var path: Path
68 | for i, entry in hzStarManVecs:
69 | let pos = entry.pos / AU
70 | if i == 0:
71 | path.moveTo(pos.x.float32, pos.y.float32)
72 | else:
73 | path.lineTo(pos.x.float32, pos.y.float32)
74 | path.closePath()
75 | screen.strokePath(path, color(1, 1, 1, 0.1), mat, strokeWidth = 0.02)
76 |
77 | var currentPos = computePos(hzStarManVecs, currentTime) / AU
78 | var pathBody: Path
79 | pathBody.circle(vec3(currentPos).xy, 0.02)
80 | screen.fillPath(pathBody, color(1,1,1,1), mat)
81 |
82 | tick()
83 |
84 | currentTime += 60*60*25 # day per frame
85 |
86 | currentTime = min(currentTime, toTime)
87 | if isKeyDown(KEY_SPACE):
88 | currentTime = fromTime
89 |
--------------------------------------------------------------------------------
/tests/test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | typedef struct Result_distance {
6 | double* pA;
7 | double* pB;
8 | double d;
9 | } Result_distance;
10 |
11 | double determinante3(double* a, double* v1, double* v2){
12 | return a[0] * (v1[1] * v2[2] - v1[2] * v2[1]) + a[1] * (v1[2] * v2[0] - v1[0] * v2[2]) + a[2] * (v1[0] * v2[1] - v1[1] * v2[0]);
13 | }
14 |
15 | double* cross3(double* v1, double* v2){
16 | double* v = (double*)malloc(3 * sizeof(double));
17 | v[0] = v1[1] * v2[2] - v1[2] * v2[1];
18 | v[1] = v1[2] * v2[0] - v1[0] * v2[2];
19 | v[2] = v1[0] * v2[1] - v1[1] * v2[0];
20 | return v;
21 | }
22 |
23 | double dot3(double* v1, double* v2){
24 | return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
25 | }
26 |
27 | double norma3(double* v1){
28 | double soma = 0;
29 | for (int i = 0; i < 3; i++) {
30 | soma += pow(v1[i], 2);
31 | }
32 | return sqrt(soma);
33 | }
34 |
35 | double* multiplica3(double* v1, double v){
36 | double* v2 = (double*)malloc(3 * sizeof(double));
37 | for (int i = 0; i < 3; i++) {
38 | v2[i] = v1[i] * v;
39 | }
40 | return v2;
41 | }
42 |
43 | double* soma3(double* v1, double* v2, int sinal){
44 | double* v = (double*)malloc(3 * sizeof(double));
45 | for (int i = 0; i < 3; i++) {
46 | v[i] = v1[i] + sinal * v2[i];
47 | }
48 | return v;
49 | }
50 |
51 | Result_distance* closestDistanceBetweenLines(double* a0, double* a1, double* b0, double* b1, int clampAll, int clampA0, int clampA1, int clampB0, int clampB1){
52 | double denom, det0, det1, t0, t1, d;
53 | double *A, *B, *_A, *_B, *cross, *t, *pA, *pB;
54 | Result_distance *rd = (Result_distance *)malloc(sizeof(Result_distance));
55 |
56 | if (clampAll){
57 | clampA0 = 1;
58 | clampA1 = 1;
59 | clampB0 = 1;
60 | clampB1 = 1;
61 | }
62 |
63 | A = soma3(a1, a0, -1);
64 | B = soma3(b1, b0, -1);
65 | _A = multiplica3(A, 1 / norma3(A));
66 | _B = multiplica3(B, 1 / norma3(B));
67 | cross = cross3(_A, _B);
68 | denom = pow(norma3(cross), 2);
69 |
70 | printf("denom = %f\n", denom);
71 |
72 | if (denom == 0){
73 | double d0 = dot3(_A, soma3(b0, a0, -1));
74 | d = norma3(soma3(soma3(multiplica3(_A, d0), a0, 1), b0, -1));
75 | printf("d = %f\n", d);
76 | if (clampA0 || clampA1 || clampB0 || clampB1){
77 | double d1 = dot3(_A, soma3(b1, a0, -1));
78 | if (d0 <= 0 && 0 >= d1){
79 | if (clampA0 && clampB1){
80 | if (abs(d0) < abs(d1)){
81 | rd->pA = b0;
82 | rd->pB = a0;
83 | rd->d = norma3(soma3(b0, a0, -1));
84 | }
85 | else{
86 | rd->pA = b1;
87 | rd->pB = a0;
88 | rd->d = norma3(soma3(b1, a0, -1));
89 | }
90 | }
91 | }
92 | else if (d0 >= norma3(A) && norma3(A) <= d1){
93 | if (clampA1 && clampB0){
94 | if (abs(d0) pA = b0;
96 | rd->pB = a1;
97 | rd->d = norma3(soma3(b0, a1, -1));
98 | }
99 | else{
100 | rd->pA = b1;
101 | rd->pB = a1;
102 | rd->d = norma3(soma3(b1, a1, -1));
103 | }
104 | }
105 | }
106 | }
107 | else{
108 | rd->pA = NULL;
109 | rd->pB = NULL;
110 | rd->d = d;
111 | }
112 | }
113 | else{
114 | t = soma3(b0, a0, -1);
115 | det0 = determinante3(t, _B, cross);
116 | det1 = determinante3(t, _A, cross);
117 | t0 = det0 / denom;
118 | t1 = det1 / denom;
119 | pA = soma3(a0, multiplica3(_A, t0), 1);
120 | pB = soma3(b0, multiplica3(_B, t1), 1);
121 |
122 | if (clampA0 || clampA1 || clampB0 || clampB1){
123 | if (t0 < 0 && clampA0)
124 | pA = a0;
125 | else if (t0 > norma3(A) && clampA1)
126 | pA = a1;
127 | if (t1 < 0 && clampB0)
128 | pB = b0;
129 | else if (t1 > norma3(B) && clampB1)
130 | pB = b1;
131 | }
132 |
133 | d = norma3(soma3(pA, pB, -1));
134 |
135 | rd->pA = pA;
136 | rd->pB = pB;
137 | rd->d = d;
138 | }
139 |
140 | free(A);
141 | free(B);
142 | free(cross);
143 | free(t);
144 | return rd;
145 | }
146 |
147 | int main(void){
148 | //example
149 | double a1[] = { 13.43, 21.77, 46.81 };
150 | double a0[] = { 27.83, 31.74, -26.60 };
151 | double b0[] = { 77.54, 7.53, 6.22 };
152 | double b1[] = { 26.99, 12.39, 11.18 };
153 |
154 | Result_distance* rd = closestDistanceBetweenLines(a0, a1, b0, b1, 1, 0, 0, 0, 0);
155 | printf("pA = [%f, %f, %f]\n", rd->pA[0], rd->pA[1], rd->pA[2]);
156 | printf("pB = [%f, %f, %f]\n", rd->pB[0], rd->pB[1], rd->pB[2]);
157 | printf("d = %f\n", rd->d);
158 | return 0;
159 | }
--------------------------------------------------------------------------------
/tests/test.nim:
--------------------------------------------------------------------------------
1 | import print, vmath
2 |
3 | proc det(a, v1, v2: Vec3): float =
4 | return
5 | a.x * (v1.y * v2.z - v1.z * v2.y) +
6 | a.y * (v1.z * v2.x - v1.x * v2.z) +
7 | a.z * (v1.x * v2.y - v1.y * v2.x)
8 |
9 |
10 | proc computeLineToLine(
11 | a0, a1, b0, b1: Vec3,
12 | clampA0, clampA1, clampB0, clampB1: bool
13 | ): (Vec3, Vec3, float32) =
14 |
15 | let
16 | a = a1 - a0
17 | b = b1 - b0
18 | aNorm = a.normalize()
19 | bNorm = b.normalize()
20 | cross = cross(aNorm, bNorm)
21 | denom = pow(cross.length, 2)
22 |
23 | if denom == 0:
24 | let
25 | d0 = dot(aNorm, b0 - a0)
26 | if clampA0 or clampA1 or clampB0 or clampB1:
27 | let d1 = dot(aNorm, b1 - a0)
28 | if d0 <= 0 and 0 >= d1:
29 | if clampA0 and clampB1:
30 | if abs(d0) < abs(d1):
31 | return (b0, a0, (b0 - a0).length)
32 | else:
33 | return (b1, a0, (b1 - a0).length)
34 | elif d0 >= a.length and a.length <= d1:
35 | if clampA1 and clampB0:
36 | if abs(d0) < abs(d1):
37 | return (b0, a1, (b0 - a1).length)
38 | else:
39 | return (b1, a1, (b1 - a1).length)
40 | else:
41 | let d = (aNorm * d0 + a0 - b0).length
42 | return (vec3(0,0,0), vec3(0,0,0), d)
43 | else:
44 | let
45 | t = b0 - a0
46 | det0 = det(t, bNorm, cross)
47 | det1 = det(t, aNorm, cross)
48 | t0 = det0 / denom
49 | t1 = det1 / denom
50 | var
51 | pA = a0 + aNorm * t0
52 | pB = b0 + bNorm * t1
53 |
54 | if clampA0 or clampA1 or clampB0 or clampB1:
55 | if t0 < 0 and clampA0:
56 | pA = a0
57 | elif t0 > a.length and clampA1:
58 | pA = a1
59 | if t1 < 0 and clampB0:
60 | pB = b0;
61 | elif t1 > b.length and clampB1:
62 | pB = b1;
63 |
64 | var d = (pA - pB).length
65 |
66 | return (pA, pB, d)
67 |
68 |
69 | proc computeSegToSeg(a0, a1, b0, b1: Vec3): (Vec3, Vec3, float32) =
70 | computeLineToLine(a0, a1, b0, b1, true, true, true, true)
71 |
72 |
73 |
74 | when isMainModule:
75 | proc almostEquals(a, b: float): bool = abs(a - b) < 0.00001
76 |
77 | block:
78 | let
79 | a1 = vec3(0, 0, 0)
80 | a0 = vec3(0, 1, 0)
81 | b0 = vec3(1, 0, 0)
82 | b1 = vec3(1, 1, 0)
83 | let r = computeLineToLine(a0, a1, b0, b1, false, false, false, false)
84 | assert r[0] == vec3(0, 0, 0)
85 | assert r[1] == vec3(0, 0, 0)
86 | assert r[2] == 1.0
87 |
88 |
89 | block:
90 | let
91 | a1 = vec3(0, 0, 0)
92 | a0 = vec3(0, 1, 0)
93 | b0 = vec3(100, 0, 0)
94 | b1 = vec3(100, 1, 0)
95 | let r = computeLineToLine(a0, a1, b0, b1, false, false, false, false)
96 | assert r[0] == vec3(0, 0, 0)
97 | assert r[1] == vec3(0, 0, 0)
98 | assert r[2] == 100.0
99 |
100 |
101 | block:
102 | let
103 | a1 = vec3(0, -7, 0)
104 | a0 = vec3(0, -13, 0)
105 | b0 = vec3(0, 25, 0)
106 | b1 = vec3(0, 26, 0)
107 | let r = computeLineToLine(a0, a1, b0, b1, false, false, false, false)
108 | assert r[0] == vec3(0, 0, 0)
109 | assert r[1] == vec3(0, 0, 0)
110 | assert r[2] == 0
111 |
112 | let r2 = computeLineToLine(a0, a1, b0, b1, true, true, true, true)
113 | assert r2[0] == vec3(0, 25, 0)
114 | assert r2[1] == vec3(0, -7, 0)
115 | assert r2[2] == 32
116 |
117 | block:
118 | let
119 | a1 = vec3(0, 0, 0)
120 | a0 = vec3(1, 1, 1)
121 | b0 = vec3(-1, -1, -1)
122 | b1 = vec3(-2, -2, -2)
123 | let r = computeLineToLine(a0, a1, b0, b1, true, true, true, true)
124 | assert r[0] == vec3(-1, -1, -1)
125 | assert r[1] == vec3(0, 0, 0)
126 | assert almostEquals(r[2], 1.732050807568877)
127 |
128 | block:
129 | let
130 | a1 = vec3(13.43, 21.77, 46.81)
131 | a0 = vec3(27.83, 31.74, -26.60)
132 | b0 = vec3(77.54, 7.53, 6.22)
133 | b1 = vec3(26.99, 12.39, 11.18)
134 | let dist = computeLineToLine(a0, a1, b0, b1, true, true, true, true)
135 | #let dist = computeLineToLine(a0, a1, b0, b1, false, false, false, false)
136 | #assert $dist == "((19.85163563, 26.21609078, 14.07303667), (26.99000000, 12.39000000, 11.18000000), 15.82677141213224)"
137 | # pA = [19.851636, 26.216091, 14.073037]
138 | # pB = [26.990000, 12.390000, 11.180000]
139 | # d = 15.826771
140 |
--------------------------------------------------------------------------------