ephem 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -1
- data/README.md +47 -12
- data/bin/console +11 -0
- data/bin/ruby-ephem +12 -0
- data/bin/setup +8 -0
- data/lib/ephem/cli.rb +159 -0
- data/lib/ephem/download.rb +147 -0
- data/lib/ephem/excerpt.rb +382 -0
- data/lib/ephem/segments/base_segment.rb +2 -0
- data/lib/ephem/spk.rb +34 -2
- data/lib/ephem/version.rb +1 -1
- data/lib/ephem.rb +3 -1
- metadata +53 -5
- data/lib/ephem/io/download.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24301ad81a75b670a87dd1b33e7d8c84e9e2bd9bd007e5d6f3564dbdc5d387ae
|
4
|
+
data.tar.gz: b6c16db80b49173c1aea2853774c2c1fb81bc0513d0273bb1f5564fd2043109c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28e9df83433cb9a338197c1911cf136147c05b22909932ed591ce6512be2d58304c182947cc8232437f65342c2ac2d600329fdde648b9378d63ba0770ef724a6
|
7
|
+
data.tar.gz: 4d1a96e724299ecd9c220283807eb47312ea7134698d9cffd31edb1d3be716add8da1af15b831246066ad2cd894c6249d185c3bf8bec87c2b525c931ff3b4a70
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,47 @@
|
|
1
|
-
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.2.0] - 2025-03-28
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
* Simplify download ([#12])
|
8
|
+
* SPK excerpt generator ([#13])
|
9
|
+
* Improve documentation on excerpts ([#16])
|
10
|
+
* IMCCE INPOP support ([#20])
|
11
|
+
|
12
|
+
### Improvements
|
13
|
+
|
14
|
+
* Add Dependabot ([#6])
|
15
|
+
* Replace testing kernel ([#17])
|
16
|
+
* Add `irb` to dev dependencies ([#14])
|
17
|
+
* Add support for Rubies `3.2.7` and `3.4.2` ([#15])
|
18
|
+
* Bump csv from 3.3.0 to 3.3.2 by @dependabot ([#7])
|
19
|
+
* Bump standard from 1.43.0 to 1.44.0 by @dependabot ([#8])
|
20
|
+
* Bump standard from 1.44.0 to 1.45.0 by @dependabot ([#9])
|
21
|
+
* Bump csv from 3.3.2 to 3.3.3 by @dependabot ([#11])
|
22
|
+
* Bump standard from 1.45.0 to 1.47.0 by @dependabot ([#10])
|
23
|
+
* Bump json from 2.10.1 to 2.10.2 by @dependabot ([#18])
|
24
|
+
|
25
|
+
### New Contributors
|
26
|
+
|
27
|
+
* @dependabot made their first contribution in [#7]
|
28
|
+
|
29
|
+
**Full Changelog**: https://github.com/rhannequin/ruby-ephem/compare/v0.1.0...v0.2.0
|
30
|
+
|
31
|
+
[#6]: https://github.com/rhannequin/ruby-ephem/pull/6
|
32
|
+
[#7]: https://github.com/rhannequin/ruby-ephem/pull/7
|
33
|
+
[#8]: https://github.com/rhannequin/ruby-ephem/pull/8
|
34
|
+
[#9]: https://github.com/rhannequin/ruby-ephem/pull/9
|
35
|
+
[#10]: https://github.com/rhannequin/ruby-ephem/pull/10
|
36
|
+
[#11]: https://github.com/rhannequin/ruby-ephem/pull/11
|
37
|
+
[#12]: https://github.com/rhannequin/ruby-ephem/pull/12
|
38
|
+
[#13]: https://github.com/rhannequin/ruby-ephem/pull/13
|
39
|
+
[#14]: https://github.com/rhannequin/ruby-ephem/pull/14
|
40
|
+
[#15]: https://github.com/rhannequin/ruby-ephem/pull/15
|
41
|
+
[#16]: https://github.com/rhannequin/ruby-ephem/pull/16
|
42
|
+
[#17]: https://github.com/rhannequin/ruby-ephem/pull/17
|
43
|
+
[#18]: https://github.com/rhannequin/ruby-ephem/pull/18
|
44
|
+
[#20]: https://github.com/rhannequin/ruby-ephem/pull/20
|
2
45
|
|
3
46
|
## [0.1.0] - 2025-01-01
|
4
47
|
|
data/README.md
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
# Ephem
|
2
2
|
|
3
|
-
Ephem is a Ruby gem that provides a simple interface to the
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
celestial bodies with high precision.
|
3
|
+
Ephem is a Ruby gem that provides a simple interface to the SPICE binary kernel
|
4
|
+
files such as:
|
5
|
+
* _JPL [Development Ephemeris]_ (DE)
|
6
|
+
* _IMCCE [Intégrateur numérique planétaire de l'Observatoire de Paris]_ (INPOP)
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
[Development Ephemeris]: https://ssd.jpl.nasa.gov/planets/eph_export.html
|
9
|
+
[Intégrateur numérique planétaire de l'Observatoire de Paris]: https://www.imcce.fr/inpop
|
11
10
|
|
12
|
-
|
11
|
+
These files are a collection of numerical integrations of the equations of
|
12
|
+
motion of the Solar System, used to calculate the positions of the planets,
|
13
|
+
the Moon, and other celestial bodies with high precision.
|
14
|
+
|
15
|
+
Ephem currently only support planetary ephemerides like DE421, DE430,
|
16
|
+
INPOP19A, etc.
|
17
|
+
|
18
|
+
The library is in high development mode and does not have a stable version yet.
|
13
19
|
The API is subject to major changes at the moment, please keep that in mind if
|
14
20
|
you consider adding this gem as a dependency.
|
15
21
|
|
@@ -28,14 +34,21 @@ executing:
|
|
28
34
|
gem install ephem
|
29
35
|
```
|
30
36
|
|
37
|
+
## How to select the right kernel file
|
38
|
+
|
39
|
+
JPL and IMCCE produces many different kernels over the years, with different
|
40
|
+
accuracy and ranges of supported years. Here are some that we would recommend to
|
41
|
+
begin with:
|
42
|
+
|
43
|
+
* `de421.bsp`: from 1900 to 2050, 17 MB
|
44
|
+
* `de440s.bsp`: from 1849 to 2150, 32 MB
|
45
|
+
* `inpop19a.bsp`: from 1900 to 2100, 22 MB
|
46
|
+
|
31
47
|
## Usage
|
32
48
|
|
33
49
|
```rb
|
34
50
|
# Download and store the SPICE binary kernel file
|
35
|
-
Ephem::
|
36
|
-
name: "de421.bsp",
|
37
|
-
target: "tmp/de421.bsp"
|
38
|
-
)
|
51
|
+
Ephem::Download.call(name: "de421.bsp", target: "tmp/de421.bsp")
|
39
52
|
|
40
53
|
# Load the kernel
|
41
54
|
spk = Ephem::SPK.open("tmp/de421.bsp")
|
@@ -86,6 +99,28 @@ puts "Velocity: #{state.velocity}"
|
|
86
99
|
# The velocity is expressed in km/day
|
87
100
|
```
|
88
101
|
|
102
|
+
## CLI
|
103
|
+
|
104
|
+
The gem also provides a CLI to generate an excerpt from an original kernel file.
|
105
|
+
|
106
|
+
```bash
|
107
|
+
ruby-ephem excerpt [options] START_DATE END_DATE INPUT_FILE OUTPUT_FILE
|
108
|
+
```
|
109
|
+
|
110
|
+
For example, generate an excerpt from DE440s for the year 2025 only supporting
|
111
|
+
the Sun, the Earth, the Moon and the Earth-Moon Barycenter:
|
112
|
+
|
113
|
+
```bash
|
114
|
+
ruby-ephem excerpt --targets 3,10,301,399 2025-01-01 2026-01-01 /path/to/de440s.bsp 2025_excerpt.bsp
|
115
|
+
```
|
116
|
+
|
117
|
+
While DE440s originally supports 14 segments from 1849 to 2150 with a size of
|
118
|
+
32 MB, the excerpt will only support 4 segments from 2025 to 2026 with a size of
|
119
|
+
140 KB.
|
120
|
+
|
121
|
+
Not only the excerpt is smaller, but most importantly it is way more
|
122
|
+
efficient to parse and to use in your application.
|
123
|
+
|
89
124
|
## Accuracy
|
90
125
|
|
91
126
|
Data from this library has been tested against the Python library [jplephem]
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "ephem"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "irb"
|
11
|
+
IRB.start(__FILE__)
|
data/bin/ruby-ephem
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Add the lib directory to the load path
|
5
|
+
lib = File.expand_path("../lib", __dir__)
|
6
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
7
|
+
|
8
|
+
require "ephem"
|
9
|
+
require "ephem/cli"
|
10
|
+
|
11
|
+
# Run the CLI
|
12
|
+
Ephem::CLI.start(ARGV)
|
data/bin/setup
ADDED
data/lib/ephem/cli.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "date"
|
5
|
+
|
6
|
+
module Ephem
|
7
|
+
class CLI
|
8
|
+
def self.gregorian_to_julian(date_str)
|
9
|
+
date = Date.parse(date_str)
|
10
|
+
|
11
|
+
a = (14 - date.month) / 12
|
12
|
+
y = date.year + 4800 - a
|
13
|
+
m = date.month + 12 * a - 3
|
14
|
+
|
15
|
+
date.day +
|
16
|
+
((153 * m + 2) / 5) +
|
17
|
+
365 * y +
|
18
|
+
(y / 4) -
|
19
|
+
(y / 100) +
|
20
|
+
(y / 400) -
|
21
|
+
32045
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.start(args)
|
25
|
+
if args.empty?
|
26
|
+
puts "Usage: ruby-ephem excerpt [options] START_DATE END_DATE INPUT_FILE OUTPUT_FILE"
|
27
|
+
puts "For help: ruby-ephem help"
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
if args[0] == "help"
|
32
|
+
show_help
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
if args[0] == "excerpt"
|
37
|
+
handle_excerpt(args[1..])
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Unknown command: #{args[0]}"
|
42
|
+
puts "Available commands: excerpt, help"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.show_help
|
46
|
+
puts <<~HELP
|
47
|
+
Ruby Ephem - A tool for working with JPL Ephemerides
|
48
|
+
|
49
|
+
Commands:
|
50
|
+
excerpt - Create an excerpt of an SPK file
|
51
|
+
help - Show this help message
|
52
|
+
|
53
|
+
Excerpt command:
|
54
|
+
ruby-ephem excerpt [options] START_DATE END_DATE INPUT_FILE OUTPUT_FILE
|
55
|
+
|
56
|
+
Options:
|
57
|
+
--targets TARGET_IDS - Comma-separated list of target IDs to include
|
58
|
+
(default: all targets)
|
59
|
+
|
60
|
+
Example:
|
61
|
+
ruby-ephem excerpt --targets 3,10,399 2000-01-01 2030-01-01 de440s.bsp excerpt.bsp
|
62
|
+
|
63
|
+
This will create an excerpt of de440s.bsp containing only the specified
|
64
|
+
targets (Earth-Moon barycenter, Sun, Earth) for the period from
|
65
|
+
2000-01-01 to 2030-01-01.
|
66
|
+
HELP
|
67
|
+
end
|
68
|
+
|
69
|
+
# Handle the excerpt command
|
70
|
+
def self.handle_excerpt(args)
|
71
|
+
# Parse options
|
72
|
+
options = {target_ids: nil, debug: false}
|
73
|
+
|
74
|
+
option_parser = OptionParser.new do |opts|
|
75
|
+
opts.banner = "Usage: ruby-ephem excerpt [options] START_DATE END_DATE INPUT_FILE OUTPUT_FILE"
|
76
|
+
|
77
|
+
opts.on("--targets TARGET_IDS", "Comma-separated list of target IDs to include") do |targets|
|
78
|
+
options[:target_ids] = targets.split(",").map(&:strip).map(&:to_i)
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on("--debug", "Enable debug output") do
|
82
|
+
options[:debug] = true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
begin
|
87
|
+
option_parser.parse!(args)
|
88
|
+
rescue OptionParser::InvalidOption => e
|
89
|
+
puts e.message
|
90
|
+
puts option_parser
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
if args.size < 4
|
95
|
+
puts "Not enough arguments."
|
96
|
+
puts option_parser
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
start_date_str = args[0]
|
101
|
+
end_date_str = args[1]
|
102
|
+
input_file = args[2]
|
103
|
+
output_file = args[3]
|
104
|
+
|
105
|
+
unless File.exist?(input_file)
|
106
|
+
puts "Error: Input file '#{input_file}' does not exist."
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
begin
|
111
|
+
start_jd = gregorian_to_julian(start_date_str)
|
112
|
+
end_jd = gregorian_to_julian(end_date_str)
|
113
|
+
rescue Date::Error => e
|
114
|
+
puts "Error parsing dates: #{e.message}"
|
115
|
+
puts "Dates should be in YYYY-MM-DD format."
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
begin
|
120
|
+
puts "Creating excerpt from #{input_file} to #{output_file}..."
|
121
|
+
puts "Date range: #{start_date_str} to #{end_date_str} (JD #{start_jd} to #{end_jd})"
|
122
|
+
if options[:target_ids]
|
123
|
+
puts "Including targets: #{options[:target_ids].join(", ")}"
|
124
|
+
else
|
125
|
+
puts "Including all targets"
|
126
|
+
end
|
127
|
+
|
128
|
+
spk = Ephem::SPK.open(input_file)
|
129
|
+
|
130
|
+
excerpt_spk = spk.excerpt(
|
131
|
+
output_path: output_file,
|
132
|
+
start_jd: start_jd,
|
133
|
+
end_jd: end_jd,
|
134
|
+
target_ids: options[:target_ids],
|
135
|
+
debug: options[:debug]
|
136
|
+
)
|
137
|
+
|
138
|
+
puts "Excerpt created successfully!"
|
139
|
+
puts "Original segments: #{spk.segments.size}"
|
140
|
+
puts "Excerpt segments: #{excerpt_spk.segments.size}"
|
141
|
+
|
142
|
+
original_size = File.size(input_file)
|
143
|
+
excerpt_size = File.size(output_file)
|
144
|
+
reduction_percentage =
|
145
|
+
((original_size - excerpt_size) / original_size.to_f * 100).round(2)
|
146
|
+
|
147
|
+
puts "File size reduced by #{reduction_percentage}%"
|
148
|
+
puts "Original: #{original_size} bytes"
|
149
|
+
puts "Excerpt: #{excerpt_size} bytes"
|
150
|
+
|
151
|
+
spk.close
|
152
|
+
excerpt_spk.close
|
153
|
+
rescue => e
|
154
|
+
puts "Error creating excerpt: #{e.message}"
|
155
|
+
puts e.backtrace if options[:debug]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitar"
|
4
|
+
require "net/http"
|
5
|
+
require "tempfile"
|
6
|
+
require "zlib"
|
7
|
+
|
8
|
+
module Ephem
|
9
|
+
class Download
|
10
|
+
JPL_BASE_URL = "https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/"
|
11
|
+
IMCCE_BASE_URL = "https://ftp.imcce.fr/pub/ephem/planets/"
|
12
|
+
|
13
|
+
JPL_KERNELS = %w[
|
14
|
+
de102.bsp
|
15
|
+
de200.bsp
|
16
|
+
de202.bsp
|
17
|
+
de403.bsp
|
18
|
+
de405.bsp
|
19
|
+
de405_1960_2020.bsp
|
20
|
+
de406.bsp
|
21
|
+
de410.bsp
|
22
|
+
de413.bsp
|
23
|
+
de414.bsp
|
24
|
+
de418.bsp
|
25
|
+
de421.bsp
|
26
|
+
de422.bsp
|
27
|
+
de422_1850_2050.bsp
|
28
|
+
de423.bsp
|
29
|
+
de424.bsp
|
30
|
+
de424s.bsp
|
31
|
+
de425.bsp
|
32
|
+
de430_1850-2150.bsp
|
33
|
+
de430_plus_MarsPC.bsp
|
34
|
+
de430t.bsp
|
35
|
+
de431.bsp
|
36
|
+
de432t.bsp
|
37
|
+
de433.bsp
|
38
|
+
de433_plus_MarsPC.bsp
|
39
|
+
de433t.bsp
|
40
|
+
de434.bsp
|
41
|
+
de434s.bsp
|
42
|
+
de434t.bsp
|
43
|
+
de435.bsp
|
44
|
+
de435s.bsp
|
45
|
+
de435t.bsp
|
46
|
+
de436.bsp
|
47
|
+
de436s.bsp
|
48
|
+
de436t.bsp
|
49
|
+
de438.bsp
|
50
|
+
de438_plus_MarsPC.bsp
|
51
|
+
de438s.bsp
|
52
|
+
de438t.bsp
|
53
|
+
de440.bsp
|
54
|
+
de440s.bsp
|
55
|
+
de440s_plus_MarsPC.bsp
|
56
|
+
de440t.bsp
|
57
|
+
de441.bsp
|
58
|
+
].freeze
|
59
|
+
|
60
|
+
IMCCE_KERNELS = {
|
61
|
+
"inpop10b.bsp" => "inpop10b_TDB_m100_p100_spice.bsp",
|
62
|
+
"inpop10b_large.bsp" => "inpop10b_TDB_m1000_p1000_spice.bsp",
|
63
|
+
"inpop10e.bsp" => "inpop10e_TDB_m100_p100_spice.bsp",
|
64
|
+
"inpop10e_large.bsp" => "inpop10e_TDB_m1000_p1000_spice.bsp",
|
65
|
+
"inpop13c.bsp" => "inpop13c_TDB_m100_p100_spice.bsp",
|
66
|
+
"inpop13c_large.bsp" => "inpop13c_TDB_m1000_p1000_spice.bsp",
|
67
|
+
"inpop17a.bsp" => "inpop17a_TDB_m100_p100_spice.bsp",
|
68
|
+
"inpop17a_large.bsp" => "inpop17a_TDB_m1000_p1000_spice.bsp",
|
69
|
+
"inpop19a.bsp" => "inpop19a_TDB_m100_p100_spice.bsp",
|
70
|
+
"inpop19a_large.bsp" => "inpop19a_TDB_m1000_p1000_spice.bsp",
|
71
|
+
"inpop21a.bsp" => "inpop21a_TDB_m100_p100_spice.bsp",
|
72
|
+
"inpop21a_large.bsp" => "inpop21a_TDB_m1000_p1000_spice.bsp"
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
IMCCE_KERNELS_MATCHING = {
|
76
|
+
"inpop10b.bsp" => "inpop10b/inpop10b_TDB_m100_p100_spice.tar.gz",
|
77
|
+
"inpop10b_large.bsp" => "inpop10b/inpop10b_TDB_m1000_p1000_spice.tar.gz",
|
78
|
+
"inpop10e.bsp" => "inpop10e/inpop10e_TDB_m100_p100_spice_release2.tar.gz",
|
79
|
+
"inpop10e_large.bsp" =>
|
80
|
+
"inpop10e/inpop10e_TDB_m1000_p1000_spice_release2.tar.gz",
|
81
|
+
"inpop13c.bsp" => "inpop13c/inpop13c_TDB_m100_p100_spice.tar.gz",
|
82
|
+
"inpop13c_large.bsp" => "inpop13c/inpop13c_TDB_m1000_p1000_spice.tar.gz",
|
83
|
+
"inpop17a.bsp" => "inpop17a/inpop17a_TDB_m100_p100_spice.tar.gz",
|
84
|
+
"inpop17a_large.bsp" => "inpop17a/inpop17a_TDB_m1000_p1000_spice.tar.gz",
|
85
|
+
"inpop19a.bsp" => "inpop19a/inpop19a_TDB_m100_p100_spice.tar.gz",
|
86
|
+
"inpop19a_large.bsp" => "inpop19a/inpop19a_TDB_m1000_p1000_spice.tar.gz",
|
87
|
+
"inpop21a.bsp" => "inpop21a/inpop21a_TDB_m100_p100_spice.tar.gz",
|
88
|
+
"inpop21a_large.bsp" => "inpop21a/inpop21a_TDB_m1000_p1000_spice.tar.gz"
|
89
|
+
}.freeze
|
90
|
+
|
91
|
+
SUPPORTED_KERNELS = (JPL_KERNELS + IMCCE_KERNELS.keys).freeze
|
92
|
+
|
93
|
+
def self.call(name:, target:)
|
94
|
+
new(name, target).call
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(name, local_path)
|
98
|
+
@name = name
|
99
|
+
@local_path = local_path
|
100
|
+
validate_requested_kernel!
|
101
|
+
end
|
102
|
+
|
103
|
+
def call
|
104
|
+
content = jpl_kernel? ? download_from_jpl : download_from_imcce
|
105
|
+
File.write(@local_path, content)
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def validate_requested_kernel!
|
113
|
+
unless SUPPORTED_KERNELS.include?(@name)
|
114
|
+
raise UnsupportedError,
|
115
|
+
"Kernel #{@name} is not supported by the library at the moment."
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def jpl_kernel?
|
120
|
+
JPL_KERNELS.include?(@name)
|
121
|
+
end
|
122
|
+
|
123
|
+
def download_from_jpl
|
124
|
+
uri = URI("#{JPL_BASE_URL}#{@name}")
|
125
|
+
Net::HTTP.get(uri)
|
126
|
+
end
|
127
|
+
|
128
|
+
def download_from_imcce
|
129
|
+
temp_file = Tempfile.new(%w[archive .tar.gz])
|
130
|
+
uri = URI("#{IMCCE_BASE_URL}#{IMCCE_KERNELS_MATCHING[@name]}")
|
131
|
+
content = Net::HTTP.get(uri)
|
132
|
+
temp_file.write(content)
|
133
|
+
temp_file.rewind
|
134
|
+
|
135
|
+
Zlib::GzipReader.open(temp_file.path) do |gz|
|
136
|
+
Minitar::Reader.open(gz) do |tar|
|
137
|
+
tar.each_entry do |entry|
|
138
|
+
return entry.read if entry.full_name == IMCCE_KERNELS[@name]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
ensure
|
143
|
+
temp_file.close
|
144
|
+
temp_file.unlink
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ephem
|
4
|
+
# The Excerpt class creates SPK file excerpts with reduced time spans and
|
5
|
+
# target bodies. This is useful for creating smaller files that focus only on
|
6
|
+
# the data needed for specific applications.
|
7
|
+
#
|
8
|
+
# @example Create an excerpt with specific time range and bodies
|
9
|
+
# spk = Ephem::SPK.open("de421.bsp")
|
10
|
+
# excerpt = Ephem::Excerpt.new(spk).extract(
|
11
|
+
# output_path: "excerpt.bsp",
|
12
|
+
# start_jd: 2458849.5, # January 1, 2020
|
13
|
+
# end_jd: 2459580.5, # December 31, 2021
|
14
|
+
# target_ids: [3, 10, 301, 399] # Earth-Moon, Sun, Moon, Earth
|
15
|
+
# )
|
16
|
+
class Excerpt
|
17
|
+
# Constants for time calculations
|
18
|
+
S_PER_DAY = Core::Constants::Time::SECONDS_PER_DAY
|
19
|
+
J2000_EPOCH = Core::Constants::Time::J2000_EPOCH
|
20
|
+
RECORD_SIZE = 1024
|
21
|
+
|
22
|
+
# @param spk [Ephem::SPK] The SPK object to create an excerpt from
|
23
|
+
def initialize(spk)
|
24
|
+
@spk = spk
|
25
|
+
@daf = spk.instance_variable_get(:@daf)
|
26
|
+
@binary_reader = @daf.instance_variable_get(:@binary_reader)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates an excerpt of the SPK file
|
30
|
+
#
|
31
|
+
# @param output_path [String] Path where the excerpt will be written
|
32
|
+
# @param start_jd [Float] Start time as Julian Date
|
33
|
+
# @param end_jd [Float] End time as Julian Date
|
34
|
+
# @param target_ids [Array<Integer>, nil] Optional list of target IDs to
|
35
|
+
# include
|
36
|
+
# @param debug [Boolean] Whether to print debug information
|
37
|
+
#
|
38
|
+
# @return [Ephem::SPK] A new SPK instance for the excerpt file
|
39
|
+
def extract(output_path:, start_jd:, end_jd:, target_ids: nil, debug: false)
|
40
|
+
start_seconds = seconds_since_j2000(start_jd)
|
41
|
+
end_seconds = seconds_since_j2000(end_jd)
|
42
|
+
output_file = File.open(output_path, "wb+")
|
43
|
+
copy_file_header(output_file)
|
44
|
+
initialize_summary_section(output_file)
|
45
|
+
writer = create_daf_writer(output_file, debug)
|
46
|
+
process_segments(writer, start_seconds, end_seconds, target_ids, debug)
|
47
|
+
output_file.close
|
48
|
+
|
49
|
+
SPK.open(output_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def seconds_since_j2000(jd)
|
55
|
+
(jd - J2000_EPOCH) * S_PER_DAY
|
56
|
+
end
|
57
|
+
|
58
|
+
def copy_file_header(output_file)
|
59
|
+
# Get the first record number of summaries from original DAF
|
60
|
+
fward = @daf.record_data.forward_record
|
61
|
+
|
62
|
+
# Copy file record and comments
|
63
|
+
(1...fward).each do |n|
|
64
|
+
data = @binary_reader.read_record(n)
|
65
|
+
output_file.write(data)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize_summary_section(output_file)
|
70
|
+
summary_data = "\0".ljust(RECORD_SIZE, "\0")
|
71
|
+
name_data = " ".ljust(RECORD_SIZE, "\0")
|
72
|
+
output_file.write(summary_data)
|
73
|
+
output_file.write(name_data)
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_daf_writer(output_file, debug)
|
77
|
+
writer = DAFWriter.new(output_file, debug)
|
78
|
+
fward = @daf.record_data.forward_record
|
79
|
+
writer.fward = writer.bward = fward
|
80
|
+
writer.free = (fward + 1) * (RECORD_SIZE / 8) + 1
|
81
|
+
writer.endianness = @daf.endianness
|
82
|
+
writer.setup_formats(
|
83
|
+
@daf.record_data.double_count,
|
84
|
+
@daf.record_data.integer_count
|
85
|
+
)
|
86
|
+
writer.write_file_record
|
87
|
+
|
88
|
+
writer
|
89
|
+
end
|
90
|
+
|
91
|
+
def process_segments(writer, start_seconds, end_seconds, target_ids, debug)
|
92
|
+
segments_processed = 0
|
93
|
+
segments_included = 0
|
94
|
+
|
95
|
+
# Get all summaries from the original DAF
|
96
|
+
@daf.summaries.each do |name, values|
|
97
|
+
segments_processed += 1
|
98
|
+
|
99
|
+
# Filter by target ID if specified
|
100
|
+
if target_ids && !target_ids.empty?
|
101
|
+
# The target ID is at index 2 in the summary values
|
102
|
+
target_id = values[2].to_i
|
103
|
+
|
104
|
+
unless target_ids.include?(target_id)
|
105
|
+
if debug
|
106
|
+
puts "Segment #{segments_processed} (#{name}):"
|
107
|
+
puts "Target ID #{target_id} not in requested list, skipping"
|
108
|
+
end
|
109
|
+
|
110
|
+
next
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Extract segment data
|
115
|
+
if extract_segment(
|
116
|
+
writer,
|
117
|
+
name,
|
118
|
+
values,
|
119
|
+
start_seconds,
|
120
|
+
end_seconds,
|
121
|
+
segments_processed,
|
122
|
+
debug
|
123
|
+
)
|
124
|
+
segments_included += 1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
if debug
|
129
|
+
puts "Summary:"
|
130
|
+
puts "Processed #{segments_processed} segments,"
|
131
|
+
puts "included #{segments_included} in the excerpt"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def extract_segment(
|
136
|
+
writer,
|
137
|
+
name,
|
138
|
+
values,
|
139
|
+
start_seconds,
|
140
|
+
end_seconds,
|
141
|
+
segment_index,
|
142
|
+
debug
|
143
|
+
)
|
144
|
+
# Get start and end positions of the segment in the file
|
145
|
+
start_pos, end_pos = values[-2], values[-1]
|
146
|
+
|
147
|
+
if debug
|
148
|
+
puts "Processing segment #{segment_index} (#{name}):"
|
149
|
+
puts "start=#{start_pos}, end=#{end_pos}"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Read the metadata from the end of the array
|
153
|
+
init, intlen, rsize, n = @daf.read_array(end_pos - 3, end_pos)
|
154
|
+
rsize = rsize.to_i
|
155
|
+
|
156
|
+
if debug
|
157
|
+
puts " Metadata: init=#{init}, intlen=#{intlen}, rsize=#{rsize}, n=#{n}"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Calculate which portion of the data to extract based on the date range
|
161
|
+
i = clip(0, n, ((start_seconds - init) / intlen)).to_i
|
162
|
+
j = clip(0, n, ((end_seconds - init) / intlen + 1)).to_i
|
163
|
+
|
164
|
+
puts " Date range: i=#{i}, j=#{j} out of n=#{n}" if debug
|
165
|
+
|
166
|
+
# Skip if no overlap with requested date range
|
167
|
+
if i == j
|
168
|
+
if debug
|
169
|
+
puts "Segment #{segment_index} (#{name}):"
|
170
|
+
puts "No overlap with requested date range"
|
171
|
+
end
|
172
|
+
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
|
176
|
+
# Update initial time and number of records
|
177
|
+
init += i * intlen
|
178
|
+
n = j - i
|
179
|
+
|
180
|
+
puts " New metadata: init=#{init}, n=#{n}" if debug
|
181
|
+
|
182
|
+
# Extract the relevant portion of the data
|
183
|
+
extra = 4 # Enough space for the metadata: [init intlen rsize n]
|
184
|
+
excerpt_start = start_pos + rsize * i
|
185
|
+
excerpt_end = start_pos + rsize * j + extra - 1
|
186
|
+
|
187
|
+
puts " Reading array from #{excerpt_start} to #{excerpt_end}" if debug
|
188
|
+
|
189
|
+
excerpt = @daf.read_array(excerpt_start, excerpt_end)
|
190
|
+
|
191
|
+
puts " Read #{excerpt.length} values" if debug
|
192
|
+
|
193
|
+
# Update the metadata in the excerpt
|
194
|
+
excerpt[-4..-1] = [init, intlen, rsize, n]
|
195
|
+
|
196
|
+
new_values = if values.length >= 2
|
197
|
+
[init, init + n * intlen] + values[2...-2]
|
198
|
+
else
|
199
|
+
[init, init + n * intlen]
|
200
|
+
end
|
201
|
+
|
202
|
+
puts " New values: #{new_values.inspect}" if debug
|
203
|
+
|
204
|
+
# Add the extracted array to the output file
|
205
|
+
# Modify the name to indicate it's an excerpt (X prefix)
|
206
|
+
writer.add_array(
|
207
|
+
"X#{name[1..]}".force_encoding("ASCII-8BIT"),
|
208
|
+
new_values,
|
209
|
+
excerpt
|
210
|
+
)
|
211
|
+
|
212
|
+
if debug
|
213
|
+
puts "Segment #{segment_index} (#{name}):"
|
214
|
+
puts "Included in excerpt (#{i} to #{j} of #{n})"
|
215
|
+
end
|
216
|
+
|
217
|
+
true
|
218
|
+
rescue => e
|
219
|
+
puts "Error processing segment #{segment_index} (#{name}): #{e.message}"
|
220
|
+
puts e.backtrace.join("\n") if debug
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
# Clips a value between lower and upper bounds
|
225
|
+
def clip(lower, upper, n)
|
226
|
+
n.clamp(lower, upper)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Helper class for writing DAF files
|
230
|
+
# This class handles the low-level details of DAF file format
|
231
|
+
class DAFWriter
|
232
|
+
attr_reader :file
|
233
|
+
attr_accessor :fward,
|
234
|
+
:bward,
|
235
|
+
:free,
|
236
|
+
:endianness,
|
237
|
+
:double_format,
|
238
|
+
:int_format,
|
239
|
+
:nd,
|
240
|
+
:ni,
|
241
|
+
:summary_format,
|
242
|
+
:summary_control_format,
|
243
|
+
:summary_length,
|
244
|
+
:summary_step,
|
245
|
+
:summaries_per_record
|
246
|
+
|
247
|
+
def initialize(file, debug = false)
|
248
|
+
@file = file
|
249
|
+
@debug = debug
|
250
|
+
@mutex = Mutex.new
|
251
|
+
end
|
252
|
+
|
253
|
+
def setup_formats(nd, ni)
|
254
|
+
@nd = nd
|
255
|
+
@ni = ni
|
256
|
+
|
257
|
+
# Double is always 8 bytes, int is always 4 bytes
|
258
|
+
double_size = 8
|
259
|
+
int_size = 4
|
260
|
+
|
261
|
+
# Set formats based on endianness
|
262
|
+
if @endianness == :little
|
263
|
+
@double_format = "E" # Little-endian double
|
264
|
+
@int_format = "l" # Little-endian signed long (32-bit)
|
265
|
+
else
|
266
|
+
@double_format = "G" # Big-endian double
|
267
|
+
@int_format = "l>" # Big-endian signed long (32-bit)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Create formats for summary structures
|
271
|
+
@summary_control_format =
|
272
|
+
"#{@double_format}#{@double_format}#{@double_format}"
|
273
|
+
@summary_format = @double_format.to_s * @nd + @int_format.to_s * @ni
|
274
|
+
|
275
|
+
# Calculate segment summary sizes
|
276
|
+
@summary_length = double_size * @nd + int_size * @ni
|
277
|
+
|
278
|
+
# Pad to 8 bytes
|
279
|
+
@summary_step = @summary_length + (-@summary_length % 8)
|
280
|
+
|
281
|
+
@summaries_per_record = (RECORD_SIZE - 8 * 3) / @summary_step
|
282
|
+
end
|
283
|
+
|
284
|
+
def write_file_record
|
285
|
+
@file.seek(0)
|
286
|
+
data = @file.read(RECORD_SIZE)
|
287
|
+
|
288
|
+
# Update pointers directly in the data buffer
|
289
|
+
fward_pos = 76
|
290
|
+
bward_pos = 80
|
291
|
+
free_pos = 84
|
292
|
+
|
293
|
+
if @endianness == :little
|
294
|
+
data[fward_pos, 4] = [@fward].pack("l")
|
295
|
+
data[bward_pos, 4] = [@bward].pack("l")
|
296
|
+
data[free_pos, 4] = [@free].pack("l")
|
297
|
+
else
|
298
|
+
data[fward_pos, 4] = [@fward].pack("N")
|
299
|
+
data[bward_pos, 4] = [@bward].pack("N")
|
300
|
+
data[free_pos, 4] = [@free].pack("N")
|
301
|
+
end
|
302
|
+
|
303
|
+
# Write the updated record back to the file
|
304
|
+
@file.seek(0)
|
305
|
+
@file.write(data)
|
306
|
+
end
|
307
|
+
|
308
|
+
def read_record(n)
|
309
|
+
@mutex.synchronize do
|
310
|
+
@file.seek(n * RECORD_SIZE - RECORD_SIZE)
|
311
|
+
@file.read(RECORD_SIZE)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def write_record(n, data)
|
316
|
+
@mutex.synchronize do
|
317
|
+
@file.seek(n * RECORD_SIZE - RECORD_SIZE)
|
318
|
+
@file.write(data)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def add_array(name, values, array)
|
323
|
+
record_number = @bward
|
324
|
+
data = read_record(record_number).dup
|
325
|
+
|
326
|
+
control_data = data[0, 24].unpack(@summary_control_format)
|
327
|
+
next_record = control_data[0].to_i
|
328
|
+
previous_record = control_data[1].to_i
|
329
|
+
n_summaries = control_data[2].to_i
|
330
|
+
|
331
|
+
if n_summaries < @summaries_per_record
|
332
|
+
# Add to the existing record
|
333
|
+
summary_record = record_number
|
334
|
+
data[0, 24] = [next_record, previous_record, n_summaries + 1]
|
335
|
+
.pack(@summary_control_format)
|
336
|
+
write_record(summary_record, data)
|
337
|
+
else
|
338
|
+
# Create a new record
|
339
|
+
summary_record = ((@free - 1) * 8 + 1023) / 1024 + 1
|
340
|
+
name_record = summary_record + 1
|
341
|
+
free_record = summary_record + 2
|
342
|
+
|
343
|
+
data[0, 24] = [summary_record, previous_record, n_summaries]
|
344
|
+
.pack(@summary_control_format)
|
345
|
+
write_record(record_number, data)
|
346
|
+
|
347
|
+
n_summaries = 0
|
348
|
+
summaries = [0, record_number, 1]
|
349
|
+
.pack(@summary_control_format)
|
350
|
+
.ljust(RECORD_SIZE, "\0")
|
351
|
+
names = " ".ljust(RECORD_SIZE, "\0")
|
352
|
+
write_record(summary_record, summaries)
|
353
|
+
write_record(name_record, names)
|
354
|
+
|
355
|
+
@bward = summary_record
|
356
|
+
@free = (free_record - 1) * RECORD_SIZE / 8 + 1
|
357
|
+
end
|
358
|
+
|
359
|
+
# Convert array to binary data
|
360
|
+
array_data = array.pack("#{@double_format}*")
|
361
|
+
|
362
|
+
start_word = @free
|
363
|
+
@file.seek((start_word - 1) * 8)
|
364
|
+
@file.write(array_data)
|
365
|
+
end_word = @file.tell / 8
|
366
|
+
|
367
|
+
@free = end_word + 1
|
368
|
+
write_file_record
|
369
|
+
|
370
|
+
# Using values up to nd+ni-2, then adding start_word and end_word
|
371
|
+
new_values = values[0, @nd + @ni - 2] + [start_word, end_word]
|
372
|
+
|
373
|
+
base = RECORD_SIZE * (summary_record - 1)
|
374
|
+
offset = n_summaries * @summary_step
|
375
|
+
@file.seek(base + 24 + offset) # 24 is summary_control_struct size
|
376
|
+
@file.write(new_values.pack(@summary_format))
|
377
|
+
@file.seek(base + RECORD_SIZE + offset)
|
378
|
+
@file.write(name[0, @summary_length].ljust(@summary_step, " "))
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
data/lib/ephem/spk.rb
CHANGED
@@ -15,7 +15,17 @@ module Ephem
|
|
15
15
|
# spk.close
|
16
16
|
#
|
17
17
|
class SPK
|
18
|
+
TYPES = [
|
19
|
+
INPOP = "IMCCE INPOP",
|
20
|
+
JPL_DE = "JPL DE"
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
INPOP_REGEXP = /^\s+\d{4}\.\d{5}0+$/
|
24
|
+
DE_REGEXP = /^[A-Z]E-(\d{4})LE-\1$/
|
25
|
+
DE_FILENAME = "NIO2SPK"
|
26
|
+
|
18
27
|
DATA_TYPE_IDENTIFIER = 5
|
28
|
+
SEGMENT_CLASSES = {}
|
19
29
|
|
20
30
|
attr_reader :segments, :pairs
|
21
31
|
|
@@ -77,6 +87,18 @@ module Ephem
|
|
77
87
|
end
|
78
88
|
end
|
79
89
|
|
90
|
+
# Type of SPK file to make the difference between JPL DE and IMCCE INPOP
|
91
|
+
#
|
92
|
+
# @return [String, nil] The type of the SPK file
|
93
|
+
def type
|
94
|
+
@type ||= if @daf.record_data.internal_filename.match?(INPOP_REGEXP)
|
95
|
+
INPOP
|
96
|
+
elsif @daf.record_data.internal_filename == DE_FILENAME ||
|
97
|
+
segments.first&.source&.match?(DE_REGEXP)
|
98
|
+
JPL_DE
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
80
102
|
# Returns the comments stored in the SPK file.
|
81
103
|
#
|
82
104
|
# @return [String] The comments from the DAF file
|
@@ -96,6 +118,18 @@ module Ephem
|
|
96
118
|
@segments.each(&block)
|
97
119
|
end
|
98
120
|
|
121
|
+
def excerpt(output_path:, start_jd:, end_jd:, target_ids: nil, debug: false)
|
122
|
+
Excerpt
|
123
|
+
.new(self)
|
124
|
+
.extract(
|
125
|
+
output_path: output_path,
|
126
|
+
start_jd: start_jd,
|
127
|
+
end_jd: end_jd,
|
128
|
+
target_ids: target_ids,
|
129
|
+
debug: debug
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
99
133
|
private
|
100
134
|
|
101
135
|
def load_segments
|
@@ -115,7 +149,5 @@ module Ephem
|
|
115
149
|
segment_class = SEGMENT_CLASSES.fetch(data_type, Segments::BaseSegment)
|
116
150
|
segment_class.new(daf: @daf, source: source, descriptor: descriptor)
|
117
151
|
end
|
118
|
-
|
119
|
-
SEGMENT_CLASSES = {}
|
120
152
|
end
|
121
153
|
end
|
data/lib/ephem/version.rb
CHANGED
data/lib/ephem.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative "ephem/core/vector"
|
|
9
9
|
require_relative "ephem/error"
|
10
10
|
require_relative "ephem/io/binary_reader"
|
11
11
|
require_relative "ephem/io/daf"
|
12
|
-
require_relative "ephem/
|
12
|
+
require_relative "ephem/download"
|
13
13
|
require_relative "ephem/io/endianness_manager"
|
14
14
|
require_relative "ephem/io/record_data"
|
15
15
|
require_relative "ephem/io/record_parser"
|
@@ -18,6 +18,8 @@ require_relative "ephem/spk"
|
|
18
18
|
require_relative "ephem/segments/base_segment"
|
19
19
|
require_relative "ephem/segments/registry"
|
20
20
|
require_relative "ephem/segments/segment"
|
21
|
+
require_relative "ephem/excerpt"
|
22
|
+
require_relative "ephem/cli"
|
21
23
|
require_relative "ephem/version"
|
22
24
|
|
23
25
|
module Ephem
|
metadata
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ephem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémy Hannequin
|
8
|
-
bindir:
|
8
|
+
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: minitar
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.12'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.12'
|
12
26
|
- !ruby/object:Gem::Dependency
|
13
27
|
name: numo-narray
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -23,6 +37,20 @@ dependencies:
|
|
23
37
|
- - "~>"
|
24
38
|
- !ruby/object:Gem::Version
|
25
39
|
version: 0.9.2.1
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: zlib
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.2'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.2'
|
26
54
|
- !ruby/object:Gem::Dependency
|
27
55
|
name: csv
|
28
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -37,6 +65,20 @@ dependencies:
|
|
37
65
|
- - "~>"
|
38
66
|
- !ruby/object:Gem::Version
|
39
67
|
version: '3.3'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: irb
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.15'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.15'
|
40
82
|
- !ruby/object:Gem::Dependency
|
41
83
|
name: parallel
|
42
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,7 +139,8 @@ description: Ruby implementation of the parsing and computation of ephemerides f
|
|
97
139
|
NASA JPL Development Ephemerides DE4xx
|
98
140
|
email:
|
99
141
|
- remy.hannequin@gmail.com
|
100
|
-
executables:
|
142
|
+
executables:
|
143
|
+
- ruby-ephem
|
101
144
|
extensions: []
|
102
145
|
extra_rdoc_files: []
|
103
146
|
files:
|
@@ -109,17 +152,22 @@ files:
|
|
109
152
|
- LICENSE.txt
|
110
153
|
- README.md
|
111
154
|
- Rakefile
|
155
|
+
- bin/console
|
156
|
+
- bin/ruby-ephem
|
157
|
+
- bin/setup
|
112
158
|
- lib/ephem.rb
|
159
|
+
- lib/ephem/cli.rb
|
113
160
|
- lib/ephem/computation/chebyshev_polynomial.rb
|
114
161
|
- lib/ephem/core/calendar_calculations.rb
|
115
162
|
- lib/ephem/core/constants/bodies.rb
|
116
163
|
- lib/ephem/core/constants/time.rb
|
117
164
|
- lib/ephem/core/state.rb
|
118
165
|
- lib/ephem/core/vector.rb
|
166
|
+
- lib/ephem/download.rb
|
119
167
|
- lib/ephem/error.rb
|
168
|
+
- lib/ephem/excerpt.rb
|
120
169
|
- lib/ephem/io/binary_reader.rb
|
121
170
|
- lib/ephem/io/daf.rb
|
122
|
-
- lib/ephem/io/download.rb
|
123
171
|
- lib/ephem/io/endianness_manager.rb
|
124
172
|
- lib/ephem/io/record_data.rb
|
125
173
|
- lib/ephem/io/record_parser.rb
|
data/lib/ephem/io/download.rb
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "net/http"
|
4
|
-
|
5
|
-
module Ephem
|
6
|
-
module IO
|
7
|
-
class Download
|
8
|
-
BASE_URL = "https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/"
|
9
|
-
|
10
|
-
SUPPORTED_KERNELS = %w[
|
11
|
-
de102.bsp
|
12
|
-
de200.bsp
|
13
|
-
de202.bsp
|
14
|
-
de403.bsp
|
15
|
-
de405.bsp
|
16
|
-
de405_1960_2020.bsp
|
17
|
-
de406.bsp
|
18
|
-
de410.bsp
|
19
|
-
de413.bsp
|
20
|
-
de414.bsp
|
21
|
-
de418.bsp
|
22
|
-
de421.bsp
|
23
|
-
de422.bsp
|
24
|
-
de422_1850_2050.bsp
|
25
|
-
de423.bsp
|
26
|
-
de424.bsp
|
27
|
-
de424s.bsp
|
28
|
-
de425.bsp
|
29
|
-
de430_1850-2150.bsp
|
30
|
-
de430_plus_MarsPC.bsp
|
31
|
-
de430t.bsp
|
32
|
-
de431.bsp
|
33
|
-
de432t.bsp
|
34
|
-
de433.bsp
|
35
|
-
de433_plus_MarsPC.bsp
|
36
|
-
de433t.bsp
|
37
|
-
de434.bsp
|
38
|
-
de434s.bsp
|
39
|
-
de434t.bsp
|
40
|
-
de435.bsp
|
41
|
-
de435s.bsp
|
42
|
-
de435t.bsp
|
43
|
-
de436.bsp
|
44
|
-
de436s.bsp
|
45
|
-
de436t.bsp
|
46
|
-
de438.bsp
|
47
|
-
de438_plus_MarsPC.bsp
|
48
|
-
de438s.bsp
|
49
|
-
de438t.bsp
|
50
|
-
de440.bsp
|
51
|
-
de440s.bsp
|
52
|
-
de440s_plus_MarsPC.bsp
|
53
|
-
de440t.bsp
|
54
|
-
de441.bsp
|
55
|
-
].freeze
|
56
|
-
|
57
|
-
def self.call(name:, target:)
|
58
|
-
new(name, target).call
|
59
|
-
end
|
60
|
-
|
61
|
-
def initialize(name, local_path)
|
62
|
-
@name = name
|
63
|
-
@local_path = local_path
|
64
|
-
validate_requested_kernel!
|
65
|
-
end
|
66
|
-
|
67
|
-
def call
|
68
|
-
uri = URI("#{BASE_URL}#{@name}")
|
69
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
70
|
-
request = Net::HTTP::Get.new(uri)
|
71
|
-
http.request(request) do |response|
|
72
|
-
File.open(@local_path, "wb") do |file|
|
73
|
-
response.read_body do |chunk|
|
74
|
-
file.write(chunk)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def validate_requested_kernel!
|
84
|
-
unless SUPPORTED_KERNELS.include?(@name)
|
85
|
-
raise UnsupportedError,
|
86
|
-
"Kernel #{@name} is not supported by the library at the moment."
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|