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.
@@ -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
@@ -8,7 +8,7 @@ module Skymap
8
8
  include Enumerable
9
9
 
10
10
  PATH = File.expand_path(
11
- "../../../data/constellations/iau.csv",
11
+ "../../../data/constellations/iau/lines.csv",
12
12
  __dir__
13
13
  )
14
14
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skymap
4
+ ConstellationBoundary = Data.define(:constellations, :points)
5
+ end
@@ -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
- coordinates = star.equatorial_coordinates
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(end_stars.map { |star| context.horizontal(star) })
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
- project_direction(
29
+ Horizon.project(
28
30
  context,
29
- horizon_crossing(directions[1 - index], directions[index])
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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skymap
4
+ module Renderer
5
+ Boundary = Data.define(:points, :width)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skymap
4
+ module Renderer
5
+ Moon = Data.define(:center, :radius, :illuminated_fraction, :rotation)
6
+ end
7
+ end
@@ -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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skymap
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.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