skymap 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 +20 -0
- data/README.md +82 -3
- data/data/constellations/iau/boundaries.csv +5110 -0
- data/lib/skymap/catalog/constellation_boundaries.rb +43 -0
- data/lib/skymap/catalog/constellation_lines.rb +1 -1
- data/lib/skymap/constellation_boundary.rb +5 -0
- data/lib/skymap/context.rb +5 -2
- data/lib/skymap/horizon.rb +32 -0
- data/lib/skymap/layers/constellation_boundaries.rb +67 -0
- data/lib/skymap/layers/constellation_lines.rb +5 -30
- data/lib/skymap/layers/moon.rb +40 -0
- data/lib/skymap/renderer/boundary.rb +7 -0
- data/lib/skymap/renderer/moon.rb +7 -0
- data/lib/skymap/renderer/svg.rb +47 -0
- data/lib/skymap/version.rb +1 -1
- data/lib/skymap.rb +7 -0
- metadata +10 -2
- /data/data/constellations/{iau.csv → iau/lines.csv} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
|
|
5
|
+
module Skymap
|
|
6
|
+
module Catalog
|
|
7
|
+
class ConstellationBoundaries
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
PATH = File.expand_path(
|
|
11
|
+
"../../../data/constellations/iau/boundaries.csv",
|
|
12
|
+
__dir__
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def each
|
|
16
|
+
CSV.foreach(PATH, headers: true).chunk_while do |previous, row|
|
|
17
|
+
previous["border"] == row["border"]
|
|
18
|
+
end.each do |rows|
|
|
19
|
+
yield boundary_from(rows)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def boundary_from(rows)
|
|
26
|
+
ConstellationBoundary.new(
|
|
27
|
+
constellations: rows.first["constellations"].split,
|
|
28
|
+
points: rows.map { |row| coordinates_from(row) }
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def coordinates_from(row)
|
|
33
|
+
Astronoby::Coordinates::Equatorial.new(
|
|
34
|
+
right_ascension: Astronoby::Angle.from_hours(
|
|
35
|
+
row["right_ascension"].to_f
|
|
36
|
+
),
|
|
37
|
+
declination: Astronoby::Angle.from_degrees(row["declination"].to_f),
|
|
38
|
+
epoch: Astronoby::JulianDate::J2000
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/skymap/context.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Skymap
|
|
4
4
|
class Context
|
|
5
|
-
attr_reader :canvas
|
|
5
|
+
attr_reader :observer, :instant, :canvas
|
|
6
6
|
|
|
7
7
|
def initialize(observer:, instant:, canvas:)
|
|
8
8
|
@observer = observer
|
|
@@ -13,7 +13,10 @@ module Skymap
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def horizontal(star)
|
|
16
|
-
|
|
16
|
+
horizontal_coordinates(star.equatorial_coordinates)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def horizontal_coordinates(coordinates)
|
|
17
20
|
key = [
|
|
18
21
|
coordinates.right_ascension,
|
|
19
22
|
coordinates.declination,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skymap
|
|
4
|
+
module Horizon
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def direction(horizontals)
|
|
8
|
+
vectors = horizontals.map do |horizontal|
|
|
9
|
+
[
|
|
10
|
+
horizontal.altitude.cos * horizontal.azimuth.cos,
|
|
11
|
+
horizontal.altitude.cos * horizontal.azimuth.sin,
|
|
12
|
+
horizontal.altitude.sin
|
|
13
|
+
]
|
|
14
|
+
end
|
|
15
|
+
vectors.transpose.map(&:sum)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def crossing(above, below)
|
|
19
|
+
above.zip(below).map do |from, to|
|
|
20
|
+
above.last * to - below.last * from
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def project(context, direction)
|
|
25
|
+
x, y, = direction
|
|
26
|
+
context.project(
|
|
27
|
+
altitude: Astronoby::Angle.zero,
|
|
28
|
+
azimuth: Astronoby::Angle.from_radians(Math.atan2(y, x))
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skymap
|
|
4
|
+
module Layers
|
|
5
|
+
class ConstellationBoundaries
|
|
6
|
+
LINE_WIDTH_RATIO = 0.0017
|
|
7
|
+
|
|
8
|
+
def initialize(boundaries:)
|
|
9
|
+
@boundaries = boundaries
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def elements(context)
|
|
13
|
+
width = LINE_WIDTH_RATIO * context.canvas.radius
|
|
14
|
+
|
|
15
|
+
@boundaries.flat_map do |boundary|
|
|
16
|
+
visible_runs(context, boundary.points).map do |points|
|
|
17
|
+
Renderer::Boundary.new(points: points, width: width)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def visible_runs(context, coordinates)
|
|
25
|
+
horizontals = coordinates.map do |point|
|
|
26
|
+
context.horizontal_coordinates(point)
|
|
27
|
+
end
|
|
28
|
+
path = [position(context, horizontals.first)] +
|
|
29
|
+
horizontals.each_cons(2).flat_map do |from, to|
|
|
30
|
+
crossing(context, from, to) + [position(context, to)]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
path.chunk { |point| !point.nil? }.filter_map do |visible, points|
|
|
34
|
+
points if visible && points.size > 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def position(context, horizontal)
|
|
39
|
+
return if below?(horizontal)
|
|
40
|
+
|
|
41
|
+
context.project(
|
|
42
|
+
altitude: horizontal.altitude,
|
|
43
|
+
azimuth: horizontal.azimuth
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def crossing(context, from, to)
|
|
48
|
+
return [] if below?(from) == below?(to)
|
|
49
|
+
|
|
50
|
+
above, below = below?(from) ? [to, from] : [from, to]
|
|
51
|
+
[
|
|
52
|
+
Horizon.project(
|
|
53
|
+
context,
|
|
54
|
+
Horizon.crossing(
|
|
55
|
+
Horizon.direction([above]),
|
|
56
|
+
Horizon.direction([below])
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def below?(horizontal)
|
|
63
|
+
horizontal.altitude.negative?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -18,15 +18,17 @@ module Skymap
|
|
|
18
18
|
next if ends.flatten.include?(nil)
|
|
19
19
|
|
|
20
20
|
directions = ends.map do |end_stars|
|
|
21
|
-
direction(
|
|
21
|
+
Horizon.direction(
|
|
22
|
+
end_stars.map { |star| context.horizontal(star) }
|
|
23
|
+
)
|
|
22
24
|
end
|
|
23
25
|
next if directions.all? { |direction| direction.last.negative? }
|
|
24
26
|
|
|
25
27
|
from, to = ends.each_with_index.map do |end_stars, index|
|
|
26
28
|
if directions[index].last.negative?
|
|
27
|
-
|
|
29
|
+
Horizon.project(
|
|
28
30
|
context,
|
|
29
|
-
|
|
31
|
+
Horizon.crossing(directions[1 - index], directions[index])
|
|
30
32
|
)
|
|
31
33
|
else
|
|
32
34
|
Point.midpoint(end_stars.map { |star| context.position(star) })
|
|
@@ -39,33 +41,6 @@ module Skymap
|
|
|
39
41
|
)
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def direction(horizontals)
|
|
46
|
-
vectors = horizontals.map do |horizontal|
|
|
47
|
-
[
|
|
48
|
-
horizontal.altitude.cos * horizontal.azimuth.cos,
|
|
49
|
-
horizontal.altitude.cos * horizontal.azimuth.sin,
|
|
50
|
-
horizontal.altitude.sin
|
|
51
|
-
]
|
|
52
|
-
end
|
|
53
|
-
vectors.transpose.map(&:sum)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def horizon_crossing(above, below)
|
|
57
|
-
above.zip(below).map do |from, to|
|
|
58
|
-
above.last * to - below.last * from
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def project_direction(context, direction)
|
|
63
|
-
x, y, = direction
|
|
64
|
-
context.project(
|
|
65
|
-
altitude: Astronoby::Angle.zero,
|
|
66
|
-
azimuth: Astronoby::Angle.from_radians(Math.atan2(y, x))
|
|
67
|
-
)
|
|
68
|
-
end
|
|
69
44
|
end
|
|
70
45
|
end
|
|
71
46
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Skymap
|
|
4
|
+
module Layers
|
|
5
|
+
class Moon
|
|
6
|
+
MOON_RADIUS_RATIO = 0.03
|
|
7
|
+
|
|
8
|
+
def initialize(ephem:)
|
|
9
|
+
@ephem = ephem
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def elements(context)
|
|
13
|
+
moon = Astronoby::Moon.at(context.instant, ephem: @ephem)
|
|
14
|
+
horizontal = moon.observed_by(context.observer).horizontal
|
|
15
|
+
return [] if horizontal.altitude.negative?
|
|
16
|
+
|
|
17
|
+
[
|
|
18
|
+
Renderer::Moon.new(
|
|
19
|
+
center: context.project(
|
|
20
|
+
altitude: horizontal.altitude,
|
|
21
|
+
azimuth: horizontal.azimuth
|
|
22
|
+
),
|
|
23
|
+
radius: MOON_RADIUS_RATIO * context.canvas.radius,
|
|
24
|
+
illuminated_fraction: moon.illuminated_fraction,
|
|
25
|
+
rotation: rotation(moon, context.observer)
|
|
26
|
+
)
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def rotation(moon, observer)
|
|
33
|
+
from_zenith = moon.bright_limb_position_angle.degrees -
|
|
34
|
+
moon.parallactic_angle(observer: observer).degrees
|
|
35
|
+
|
|
36
|
+
-90 - from_zenith
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/skymap/renderer/svg.rb
CHANGED
|
@@ -10,6 +10,11 @@ module Skymap
|
|
|
10
10
|
HORIZON_WIDTH = 3
|
|
11
11
|
STAR_COLOR = "#ffffff"
|
|
12
12
|
LINE_COLOR = "#46689c"
|
|
13
|
+
BOUNDARY_COLOR = "#2b3f5e"
|
|
14
|
+
BOUNDARY_DASH_RATIO = 4
|
|
15
|
+
MOON_COLOR = "#f1ecd6"
|
|
16
|
+
MOON_SHADOW_COLOR = "#232a35"
|
|
17
|
+
MOON_SHADOW_OPACITY = 0.6
|
|
13
18
|
LABEL_COLOR = HORIZON_COLOR
|
|
14
19
|
LABEL_FONT = "sans-serif"
|
|
15
20
|
|
|
@@ -35,9 +40,11 @@ module Skymap
|
|
|
35
40
|
|
|
36
41
|
elements.each do |element|
|
|
37
42
|
case element
|
|
43
|
+
when Boundary then draw_boundary(svg, element)
|
|
38
44
|
when Line then draw_line(svg, element)
|
|
39
45
|
when Dot then draw_dot(svg, element)
|
|
40
46
|
when Label then draw_label(svg, element)
|
|
47
|
+
when Moon then draw_moon(svg, element)
|
|
41
48
|
else raise ArgumentError, "unknown element: #{element.class}"
|
|
42
49
|
end
|
|
43
50
|
end
|
|
@@ -59,6 +66,20 @@ module Skymap
|
|
|
59
66
|
)
|
|
60
67
|
end
|
|
61
68
|
|
|
69
|
+
def draw_boundary(svg, boundary)
|
|
70
|
+
dash = (boundary.width * BOUNDARY_DASH_RATIO).round(2)
|
|
71
|
+
|
|
72
|
+
svg.polyline(
|
|
73
|
+
points: boundary.points.map { |point|
|
|
74
|
+
"#{point.x.round(2)},#{point.y.round(2)}"
|
|
75
|
+
}.join(" "),
|
|
76
|
+
fill: "none",
|
|
77
|
+
stroke: BOUNDARY_COLOR,
|
|
78
|
+
stroke_width: boundary.width.round(2),
|
|
79
|
+
stroke_dasharray: "#{dash} #{dash}"
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
62
83
|
def draw_dot(svg, dot)
|
|
63
84
|
svg.circle(
|
|
64
85
|
cx: dot.center.x.round(2),
|
|
@@ -68,6 +89,32 @@ module Skymap
|
|
|
68
89
|
)
|
|
69
90
|
end
|
|
70
91
|
|
|
92
|
+
def draw_moon(svg, moon)
|
|
93
|
+
x = moon.center.x.round(2)
|
|
94
|
+
y = moon.center.y.round(2)
|
|
95
|
+
radius = moon.radius.round(2)
|
|
96
|
+
fraction = moon.illuminated_fraction
|
|
97
|
+
terminator = (radius * (1 - 2 * fraction).abs).round(2)
|
|
98
|
+
bulge = (fraction > 0.5) ? 1 : 0
|
|
99
|
+
top = "#{x},#{(y - radius).round(2)}"
|
|
100
|
+
bottom = "#{x},#{(y + radius).round(2)}"
|
|
101
|
+
lit = "M #{top} A #{radius},#{radius} 0 0 1 #{bottom} " \
|
|
102
|
+
"A #{terminator},#{radius} 0 0 #{bulge} #{top} Z"
|
|
103
|
+
|
|
104
|
+
svg.build do
|
|
105
|
+
g(transform: "rotate(#{moon.rotation.round(2)} #{x} #{y})") do
|
|
106
|
+
circle(
|
|
107
|
+
cx: x,
|
|
108
|
+
cy: y,
|
|
109
|
+
r: radius,
|
|
110
|
+
fill: MOON_SHADOW_COLOR,
|
|
111
|
+
fill_opacity: MOON_SHADOW_OPACITY
|
|
112
|
+
)
|
|
113
|
+
path(d: lit, fill: MOON_COLOR)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
71
118
|
def draw_label(svg, label)
|
|
72
119
|
svg.text(
|
|
73
120
|
label.text,
|
data/lib/skymap/version.rb
CHANGED
data/lib/skymap.rb
CHANGED
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
require "astronoby"
|
|
4
4
|
|
|
5
5
|
require_relative "skymap/canvas"
|
|
6
|
+
require_relative "skymap/catalog/constellation_boundaries"
|
|
6
7
|
require_relative "skymap/catalog/constellation_lines"
|
|
7
8
|
require_relative "skymap/catalog/stars"
|
|
8
9
|
require_relative "skymap/chart"
|
|
10
|
+
require_relative "skymap/constellation_boundary"
|
|
9
11
|
require_relative "skymap/constellation_line"
|
|
10
12
|
require_relative "skymap/context"
|
|
13
|
+
require_relative "skymap/horizon"
|
|
11
14
|
require_relative "skymap/layers/cardinal_directions"
|
|
15
|
+
require_relative "skymap/layers/constellation_boundaries"
|
|
12
16
|
require_relative "skymap/layers/constellation_lines"
|
|
17
|
+
require_relative "skymap/layers/moon"
|
|
13
18
|
require_relative "skymap/layers/stars"
|
|
14
19
|
require_relative "skymap/point"
|
|
15
20
|
require_relative "skymap/projection/stereographic"
|
|
21
|
+
require_relative "skymap/renderer/boundary"
|
|
16
22
|
require_relative "skymap/renderer/dot"
|
|
17
23
|
require_relative "skymap/renderer/label"
|
|
18
24
|
require_relative "skymap/renderer/line"
|
|
25
|
+
require_relative "skymap/renderer/moon"
|
|
19
26
|
require_relative "skymap/renderer/svg"
|
|
20
27
|
require_relative "skymap/star"
|
|
21
28
|
require_relative "skymap/star_size"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: skymap
|
|
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
|
|
@@ -145,23 +145,31 @@ files:
|
|
|
145
145
|
- CHANGELOG.md
|
|
146
146
|
- LICENSE.txt
|
|
147
147
|
- README.md
|
|
148
|
-
- data/constellations/iau.csv
|
|
148
|
+
- data/constellations/iau/boundaries.csv
|
|
149
|
+
- data/constellations/iau/lines.csv
|
|
149
150
|
- data/stars.csv
|
|
150
151
|
- lib/skymap.rb
|
|
151
152
|
- lib/skymap/canvas.rb
|
|
153
|
+
- lib/skymap/catalog/constellation_boundaries.rb
|
|
152
154
|
- lib/skymap/catalog/constellation_lines.rb
|
|
153
155
|
- lib/skymap/catalog/stars.rb
|
|
154
156
|
- lib/skymap/chart.rb
|
|
157
|
+
- lib/skymap/constellation_boundary.rb
|
|
155
158
|
- lib/skymap/constellation_line.rb
|
|
156
159
|
- lib/skymap/context.rb
|
|
160
|
+
- lib/skymap/horizon.rb
|
|
157
161
|
- lib/skymap/layers/cardinal_directions.rb
|
|
162
|
+
- lib/skymap/layers/constellation_boundaries.rb
|
|
158
163
|
- lib/skymap/layers/constellation_lines.rb
|
|
164
|
+
- lib/skymap/layers/moon.rb
|
|
159
165
|
- lib/skymap/layers/stars.rb
|
|
160
166
|
- lib/skymap/point.rb
|
|
161
167
|
- lib/skymap/projection/stereographic.rb
|
|
168
|
+
- lib/skymap/renderer/boundary.rb
|
|
162
169
|
- lib/skymap/renderer/dot.rb
|
|
163
170
|
- lib/skymap/renderer/label.rb
|
|
164
171
|
- lib/skymap/renderer/line.rb
|
|
172
|
+
- lib/skymap/renderer/moon.rb
|
|
165
173
|
- lib/skymap/renderer/svg.rb
|
|
166
174
|
- lib/skymap/star.rb
|
|
167
175
|
- lib/skymap/star_size.rb
|
|
File without changes
|