skymap 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36a82d2da6f139662ddad63ac5f7a61d4228d2665106c7102a828710cf3da940
4
- data.tar.gz: 446b8d777d47bc92fe8878247f61d45168f0e469180d779d304b3e66da5cb185
3
+ metadata.gz: 4c452f10b960f7fd529a6c7b686d4a002ee424966aaf725d02d6136cafbb3f39
4
+ data.tar.gz: 1f62d95726d712568a243ff936c16b3eb1154f31c8c16909575026dccef04070
5
5
  SHA512:
6
- metadata.gz: 3703c46d73bdad50017a6b00d06038ecb8876f7d7522b2699db3c1793d5e547e48511e372979be603ebf618bd6d0448140821e71e3397ec9c6b43eae58c18d88
7
- data.tar.gz: e842f5ebbc50609e9904ac20a0f284286277b9d96eb3b24c82ed50764a3452412f6384bbb9e64592e25f54b3cc7bf5b40d2f772fb532fa1be4720f44e8e890a1
6
+ metadata.gz: 815b9cca8e47e204b3ded57575169dfbc6b534246e2bc62b3fdb1c60770e3894b6c1e1215a04a7af3d239f9403a91861c11118926170df577b3923ed8c2f9e0b
7
+ data.tar.gz: 8eaa436a22575fcf838b8d6898d1cff408acacfae56f17e15675dd513e092207f78db778c1e3d0da0ecd99603747b8715752029e04941b2edbb709d894e94a5a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-11-05
11
+
12
+ ### Added
13
+
14
+ - `Skymap::Theme` gives the colors and the font of the map, with the
15
+ `Skymap::Theme.dark` (default), `Skymap::Theme.light` and
16
+ `Skymap::Theme.night` themes, and is given to `Skymap::Chart` with `theme:`
17
+ - `Skymap::Layers::StarNames` writes the names of the brightest stars, down to
18
+ a `magnitude_limit:` (2 by default), from `Skymap::Catalog::StarNames` or the
19
+ names given with `names:`, and placed for the stars drawn down to
20
+ `stars_magnitude_limit:` (5 by default)
21
+ - `Skymap::Catalog::StarNames` gives the IAU name of 333 stars of the Bright
22
+ Star Catalogue, such as Sirius or Betelgeuse
23
+
24
+ ### Changed
25
+
26
+ - The names of the planets next to the horizon move towards the center of the
27
+ map until they fit in the sky
28
+
10
29
  ## [0.3.0] - 2026-11-01
11
30
 
12
31
  ### Added
@@ -79,7 +98,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
79
98
 
80
99
  - Initial release to reserve the gem name on RubyGems.org
81
100
 
82
- [Unreleased]: https://github.com/rhannequin/skymap/compare/v0.3.0...HEAD
101
+ [Unreleased]: https://github.com/rhannequin/skymap/compare/v0.4.0...HEAD
102
+ [0.4.0]: https://github.com/rhannequin/skymap/compare/v0.3.0...v0.4.0
83
103
  [0.3.0]: https://github.com/rhannequin/skymap/compare/v0.2.0...v0.3.0
84
104
  [0.2.0]: https://github.com/rhannequin/skymap/compare/v0.1.0...v0.2.0
85
105
  [0.1.0]: https://github.com/rhannequin/skymap/compare/v0.0.1...v0.1.0
data/README.md CHANGED
@@ -8,8 +8,8 @@ Skymap is a Ruby library for generating maps of the sky.
8
8
  <p align="center">
9
9
  <img
10
10
  src="https://raw.githubusercontent.com/rhannequin/skymap/main/sky_map.svg"
11
- width="480"
12
- alt="The sky over Paris on September 24, 2026 at 22:00 UTC, with the stars, the constellation lines, boundaries and names, the ecliptic, the Moon and Saturn"
11
+ width="640"
12
+ alt="The sky over Paris on September 23, 2026 at 20:00 UTC, with the stars, the constellation lines, boundaries and names, the ecliptic, the Moon and Saturn"
13
13
  >
14
14
  </p>
15
15
 
@@ -34,29 +34,34 @@ gem install skymap
34
34
  ## Usage
35
35
 
36
36
  Skymap draws the sky seen by an observer at a given instant, and returns it
37
- as an SVG string:
37
+ as an SVG string. This is the map above:
38
38
 
39
39
  ```ruby
40
40
  require "skymap"
41
41
 
42
+ Astronoby::Ephem.download(name: "inpop19a.bsp", target: "inpop19a.bsp")
43
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
44
+
42
45
  observer = Astronoby::Observer.new(
43
46
  latitude: Astronoby::Angle.from_degrees(48.8575),
44
47
  longitude: Astronoby::Angle.from_degrees(2.3514)
45
48
  )
46
- instant = Astronoby::Instant.from_time(Time.utc(2026, 9, 24, 22))
49
+ instant = Astronoby::Instant.from_time(Time.utc(2026, 9, 23, 20))
47
50
  canvas = Skymap::Canvas.new(size: 800, padding: 24)
48
51
  stars = Skymap::Catalog::Stars.new.to_a
52
+ lines = Skymap::Catalog::ConstellationLines.new.to_a
49
53
 
50
54
  chart = Skymap::Chart.new(observer: observer, instant: instant, canvas: canvas)
51
55
  svg = chart.render(
52
56
  Skymap::Layers::ConstellationBoundaries.new(
53
57
  boundaries: Skymap::Catalog::ConstellationBoundaries.new
54
58
  ),
55
- Skymap::Layers::ConstellationLines.new(
56
- lines: Skymap::Catalog::ConstellationLines.new,
57
- stars: stars
58
- ),
59
+ Skymap::Layers::Ecliptic.new,
60
+ Skymap::Layers::ConstellationLines.new(lines: lines, stars: stars),
59
61
  Skymap::Layers::Stars.new(stars: stars),
62
+ Skymap::Layers::ConstellationNames.new(lines: lines, stars: stars),
63
+ Skymap::Layers::Planets.new(ephem: ephem, labels: true),
64
+ Skymap::Layers::Moon.new(ephem: ephem),
60
65
  Skymap::Layers::CardinalDirections.new
61
66
  )
62
67
 
@@ -64,7 +69,12 @@ File.write("sky_map.svg", svg)
64
69
  ```
65
70
 
66
71
  The observer and the instant are [Astronoby] objects. Astronoby computes where
67
- each star is in the sky and Skymap draws it.
72
+ each star is in the sky and Skymap draws it. The Moon and the planets need an
73
+ ephemeris, which the first lines download and load (see [Moon](#moon)). Every
74
+ layer is optional: leave one out to hide what it draws.
75
+
76
+ The map at the top of this page is generated by `bin/build_readme_map`, which
77
+ runs this code with the ephemeris of the tests.
68
78
 
69
79
  The map is a disk: the zenith is at the center and the horizon is the edge. It
70
80
  uses a [stereographic projection], the most common one for sky charts because
@@ -77,6 +87,65 @@ The canvas is the square SVG the map is drawn on. `size` is its width and
77
87
  height, and `padding` is the space between the horizon and the edge of the
78
88
  SVG.
79
89
 
90
+ ### Themes
91
+
92
+ A theme gives the colors of the map. `Chart` draws the dark theme by default,
93
+ light stars on a black sky. Pass `theme:` to draw another one:
94
+
95
+ ```ruby
96
+ chart = Skymap::Chart.new(
97
+ observer: observer,
98
+ instant: instant,
99
+ canvas: canvas,
100
+ theme: Skymap::Theme.light
101
+ )
102
+ ```
103
+
104
+ - `Skymap::Theme.dark`: the default.
105
+ - `Skymap::Theme.light`: dark stars on a white sky, for printing.
106
+ - `Skymap::Theme.night`: shades of red on a black sky, which keep your eyes
107
+ adapted to the dark when you use the map outside.
108
+
109
+ <p align="center">
110
+ <img
111
+ src="https://raw.githubusercontent.com/rhannequin/skymap/main/sky_map.svg"
112
+ width="30%"
113
+ alt="The dark theme: white stars on a black sky"
114
+ >
115
+ <img
116
+ src="https://raw.githubusercontent.com/rhannequin/skymap/main/docs/themes/light.svg"
117
+ width="30%"
118
+ alt="The light theme: dark stars on a white sky"
119
+ >
120
+ <img
121
+ src="https://raw.githubusercontent.com/rhannequin/skymap/main/docs/themes/night.svg"
122
+ width="30%"
123
+ alt="The night theme: shades of red on a black sky"
124
+ >
125
+ </p>
126
+
127
+ Build your own from a built-in one, changing the colors you want. Each color
128
+ is a string SVG accepts, such as `"#ffeedd"` or `"gold"`:
129
+
130
+ ```ruby
131
+ theme = Skymap::Theme.dark.with(
132
+ sky: "#0b1026",
133
+ star: "#fff4d6"
134
+ )
135
+ ```
136
+
137
+ The colors are `background` (the square behind the sky, which the cardinal
138
+ directions are written on), `sky`, `horizon`, `star`, `constellation_line`, `boundary`,
139
+ `ecliptic`, `planet`, `sun`, `moon`, `moon_shadow` (with its
140
+ `moon_shadow_opacity`, between 0 and 1), `constellation_name`, `star_name`,
141
+ `planet_name` and `label` (the cardinal directions). `font` is the font family
142
+ of every text.
143
+
144
+ Use `"none"` as a color to leave something out. The dark theme has
145
+ `background: "none"`, so the SVG is transparent around the sky, and with
146
+ `sky: "none"` the sky is transparent too, to put the map on a background of
147
+ your own. With `horizon: "none"`, the edge of the sky is not outlined.
148
+
80
149
  ### Layers
81
150
 
82
151
  A map is the sky, with the layers given to `Chart#render` drawn on top of it,
@@ -98,6 +167,46 @@ fewer stars:
98
167
  Skymap::Layers::Stars.new(stars: stars, magnitude_limit: 6)
99
168
  ```
100
169
 
170
+ #### Star names
171
+
172
+ `Skymap::Layers::StarNames` writes the names of the brightest stars, next to
173
+ them, on the side of the center of the map. A name next to the horizon is moved
174
+ towards the center until it fits in the sky, as far as the width of the text
175
+ can be estimated. It takes the same stars as the stars layer, and
176
+ `Skymap::Catalog::StarNames` gives the names of 333 stars of the catalog, as
177
+ approved by the IAU (see [Data sources](#data-sources)):
178
+
179
+ ```ruby
180
+ Skymap::Layers::StarNames.new(stars: stars)
181
+ ```
182
+
183
+ Only the stars of magnitude 2 or brighter are named by default, which is about
184
+ fifty stars. Use `magnitude_limit:` to name more or fewer stars:
185
+
186
+ ```ruby
187
+ Skymap::Layers::StarNames.new(stars: stars, magnitude_limit: 3)
188
+ ```
189
+
190
+ Names sit just outside the dots of the stars. Dots get smaller as the stars
191
+ layer draws fainter stars, so if you give it a `magnitude_limit:` other than 5,
192
+ give the same one to `stars_magnitude_limit:`:
193
+
194
+ ```ruby
195
+ Skymap::Layers::Stars.new(stars: stars, magnitude_limit: 6)
196
+ Skymap::Layers::StarNames.new(stars: stars, stars_magnitude_limit: 6)
197
+ ```
198
+
199
+ Use `names:` to write the names differently: it takes anything that gives a
200
+ name from the Harvard Revised number of a star (`hr`), such as a hash. A star
201
+ without a name is left out:
202
+
203
+ ```ruby
204
+ Skymap::Layers::StarNames.new(
205
+ stars: stars,
206
+ names: {2491 => "Sirius", 424 => "Étoile polaire"}
207
+ )
208
+ ```
209
+
101
210
  #### Constellation boundaries
102
211
 
103
212
  `Skymap::Layers::ConstellationBoundaries` draws the boundaries between the
@@ -259,6 +368,15 @@ Skymap::Layers::Ecliptic.new
259
368
  W) in the padding, with a size that follows it: a padding of about 3% of the
260
369
  size gives readable labels.
261
370
 
371
+ ## Examples
372
+
373
+ The [examples](https://github.com/rhannequin/skymap/tree/main/examples)
374
+ folder has scripts and the maps they draw, for
375
+ different combinations of layers, themes and observers: the naked-eye sky, a
376
+ printable map of the constellations, the planets, a night vision map for
377
+ outdoors, the southern sky and more. Start from the one closest to what you
378
+ want to draw.
379
+
262
380
  ## Data sources
263
381
 
264
382
  ### Stars
@@ -279,6 +397,19 @@ This research has made use of the VizieR catalogue access tool, CDS,
279
397
  Strasbourg, France ([DOI: 10.26093/cds/vizier][vizier-doi]). The original
280
398
  description of the VizieR service was published in 2000, A&AS 143, 23.
281
399
 
400
+ ### Star names
401
+
402
+ `data/star_names.csv` gives the name of 333 stars of `data/stars.csv` from
403
+ their Harvard Revised number (`hr`), as listed in the [IAU Catalog of Star
404
+ Names][iau-csn] by the IAU Working Group on Star Names. The catalog also names
405
+ stars that are not in the Bright Star Catalogue, such as the host stars of
406
+ exoplanets, which are left out.
407
+
408
+ The file is generated with `bin/build_star_names`, which downloads the list
409
+ and keeps the stars with an HR number. The IAU asks to cite its official
410
+ version of the list at <https://www.iau.org/public/themes/naming_stars/>, and
411
+ releases its products under a Creative Commons Attribution license.
412
+
282
413
  ### Constellations
283
414
 
284
415
  `data/constellations/iau/lines.csv` holds the lines of the 88 IAU
@@ -397,3 +528,4 @@ rooms and mailing lists is expected to follow the
397
528
  [jpl-ephemerides]: https://ssd.jpl.nasa.gov/planets/eph_export.html
398
529
  [naif]: https://naif.jpl.nasa.gov/naif/rules.html
399
530
  [cc-by-4]: https://creativecommons.org/licenses/by/4.0/
531
+ [iau-csn]: https://www.pas.rochester.edu/~emamajek/WGSN/IAU-CSN.txt
@@ -0,0 +1,334 @@
1
+ hr,name
2
+ 15,Alpheratz
3
+ 21,Caph
4
+ 39,Algenib
5
+ 99,Ankaa
6
+ 153,Fulu
7
+ 168,Schedar
8
+ 188,Diphda
9
+ 219,Achird
10
+ 265,Castula
11
+ 337,Mirach
12
+ 338,Wurren
13
+ 361,Revati
14
+ 390,Adhil
15
+ 403,Ruchbah
16
+ 424,Polaris
17
+ 437,Alpherg
18
+ 458,Titawin
19
+ 464,Nembus
20
+ 472,Achernar
21
+ 510,Torcular
22
+ 539,Baten Kaitos
23
+ 542,Segin
24
+ 544,Mothallah
25
+ 546,Mesarthim
26
+ 553,Sheratan
27
+ 596,Alrescha
28
+ 603,Almach
29
+ 617,Hamal
30
+ 681,Mira
31
+ 804,Kaffaljidhma
32
+ 824,Lilii Borea
33
+ 834,Miram
34
+ 838,Bharani
35
+ 850,Angetenar
36
+ 874,Azha
37
+ 897,Acamar
38
+ 911,Menkar
39
+ 936,Algol
40
+ 941,Misam
41
+ 951,Botein
42
+ 963,Dalim
43
+ 984,Zibal
44
+ 1017,Mirfak
45
+ 1084,Ran
46
+ 1131,Atik
47
+ 1136,Rana
48
+ 1140,Celaeno
49
+ 1142,Electra
50
+ 1145,Taygeta
51
+ 1149,Maia
52
+ 1151,Asterope
53
+ 1156,Merope
54
+ 1165,Alcyone
55
+ 1178,Atlas
56
+ 1180,Pleione
57
+ 1228,Menkib
58
+ 1231,Zaurak
59
+ 1298,Beid
60
+ 1325,Keid
61
+ 1346,Prima Hyadum
62
+ 1373,Secunda Hyadum
63
+ 1393,Beemim
64
+ 1409,Ain
65
+ 1412,Chamukuy
66
+ 1457,Aldebaran
67
+ 1464,Theemin
68
+ 1481,Sceptrum
69
+ 1543,Tabit
70
+ 1577,Hassaleh
71
+ 1605,Almaaz
72
+ 1612,Saclateni
73
+ 1641,Haedus
74
+ 1666,Cursa
75
+ 1708,Capella
76
+ 1713,Rigel
77
+ 1790,Bellatrix
78
+ 1791,Elnath
79
+ 1829,Nihal
80
+ 1852,Mintaka
81
+ 1865,Arneb
82
+ 1879,Meissa
83
+ 1899,Hatysa
84
+ 1903,Alnilam
85
+ 1910,Tianguan
86
+ 1948,Alnitak
87
+ 1956,Phact
88
+ 2004,Saiph
89
+ 2040,Wazn
90
+ 2061,Betelgeuse
91
+ 2088,Menkalinan
92
+ 2095,Mahasim
93
+ 2177,Elkurud
94
+ 2216,Propus
95
+ 2282,Furud
96
+ 2286,Tejat
97
+ 2294,Mirzam
98
+ 2326,Canopus
99
+ 2421,Alhena
100
+ 2473,Mebsuta
101
+ 2484,Alzirr
102
+ 2491,Sirius
103
+ 2618,Adhara
104
+ 2646,Nganurganity
105
+ 2650,Mekbuda
106
+ 2657,Muliphein
107
+ 2693,Wezen
108
+ 2777,Wasat
109
+ 2827,Aludra
110
+ 2845,Gomeisa
111
+ 2891,Castor
112
+ 2930,Jishui
113
+ 2943,Procyon
114
+ 2990,Pollux
115
+ 3045,Azmidi
116
+ 3165,Naos
117
+ 3185,Tureis
118
+ 3208,Tegmine
119
+ 3249,Tarf
120
+ 3268,Piautos
121
+ 3275,Alsciaukat
122
+ 3307,Avior
123
+ 3323,Muscida
124
+ 3418,Minchir
125
+ 3429,Meleph
126
+ 3449,Asellus Borealis
127
+ 3461,Asellus Australis
128
+ 3482,Ashlesha
129
+ 3485,Alsephina
130
+ 3522,Copernicus
131
+ 3569,Talitha
132
+ 3572,Acubens
133
+ 3594,Alkaphrah
134
+ 3627,Nahn
135
+ 3634,Suhail
136
+ 3685,Miaplacidus
137
+ 3699,Aspidiske
138
+ 3734,Markeb
139
+ 3743,Intercrus
140
+ 3748,Alphard
141
+ 3773,Alterf
142
+ 3845,Ukdah
143
+ 3852,Subra
144
+ 3903,Zhang
145
+ 3905,Rasalas
146
+ 3923,Felis
147
+ 3982,Regulus
148
+ 4031,Adhafera
149
+ 4033,Tania Borealis
150
+ 4057,Algieba
151
+ 4069,Tania Australis
152
+ 4247,Praecipua
153
+ 4277,Chalawan
154
+ 4287,Alkes
155
+ 4295,Merak
156
+ 4301,Dubhe
157
+ 4357,Zosma
158
+ 4359,Chertan
159
+ 4375,Alula Australis
160
+ 4377,Alula Borealis
161
+ 4434,Giausar
162
+ 4518,Taiyangshou
163
+ 4534,Denebola
164
+ 4540,Zavijava
165
+ 4554,Phecda
166
+ 4609,Tonatiuh
167
+ 4623,Alchiba
168
+ 4656,Imai
169
+ 4660,Megrez
170
+ 4662,Gienah
171
+ 4689,Zaniah
172
+ 4700,Ginan
173
+ 4730,Acrux
174
+ 4757,Algorab
175
+ 4763,Gacrux
176
+ 4785,Chara
177
+ 4786,Kraz
178
+ 4825,Porrima
179
+ 4846,La Superba
180
+ 4853,Mimosa
181
+ 4863,Tianyi
182
+ 4905,Alioth
183
+ 4910,Minelauva
184
+ 4915,Cor Caroli
185
+ 4916,Taiyi
186
+ 4932,Vindemiatrix
187
+ 4968,Diadem
188
+ 5054,Mizar
189
+ 5056,Spica
190
+ 5062,Alcor
191
+ 5107,Heze
192
+ 5191,Alkaid
193
+ 5235,Muphrid
194
+ 5267,Hadar
195
+ 5288,Menkent
196
+ 5291,Thuban
197
+ 5315,Kang
198
+ 5338,Syrma
199
+ 5340,Arcturus
200
+ 5351,Xuange
201
+ 5359,Khambalia
202
+ 5409,Elgafar
203
+ 5435,Seginus
204
+ 5459,Rigil Kentaurus
205
+ 5460,Toliman
206
+ 5506,Izar
207
+ 5531,Zubenelgenubi
208
+ 5533,Merga
209
+ 5563,Kochab
210
+ 5602,Nekkar
211
+ 5603,Brachium
212
+ 5685,Zubeneschamali
213
+ 5733,Alkalurops
214
+ 5735,Pherkad
215
+ 5744,Edasich
216
+ 5747,Nusakan
217
+ 5787,Zubenelhakrabi
218
+ 5793,Alphecca
219
+ 5854,Unukalhai
220
+ 5879,Gudja
221
+ 5928,Iklil
222
+ 5944,Fang
223
+ 5953,Dschubba
224
+ 5984,Acrab
225
+ 6008,Marsic
226
+ 6027,Jabbah
227
+ 6056,Yed Prior
228
+ 6075,Yed Posterior
229
+ 6084,Alniyat
230
+ 6117,Cujam
231
+ 6132,Athebyne
232
+ 6134,Antares
233
+ 6148,Kornephoros
234
+ 6149,Marfik
235
+ 6165,Paikauhale
236
+ 6217,Atria
237
+ 6241,Larawag
238
+ 6247,Xamidimura
239
+ 6252,Pipirima
240
+ 6370,Alrakis
241
+ 6378,Sabik
242
+ 6396,Aldhibah
243
+ 6402,Guniibuu
244
+ 6406,Rasalgethi
245
+ 6410,Sarin
246
+ 6508,Lesath
247
+ 6526,Maasym
248
+ 6527,Shaula
249
+ 6536,Rastaban
250
+ 6553,Sargas
251
+ 6556,Rasalhague
252
+ 6585,Cervantes
253
+ 6603,Cebalrai
254
+ 6618,Alruba
255
+ 6630,Fuyue
256
+ 6636,Dziban
257
+ 6688,Grumium
258
+ 6705,Eltanin
259
+ 6746,Alnasl
260
+ 6789,Yildun
261
+ 6812,Polis
262
+ 6859,Kaus Media
263
+ 6879,Kaus Australis
264
+ 6913,Kaus Borealis
265
+ 6945,Fafnir
266
+ 7001,Vega
267
+ 7106,Sheliak
268
+ 7116,Ainalrami
269
+ 7121,Nunki
270
+ 7141,Alya
271
+ 7178,Sulafat
272
+ 7194,Ascella
273
+ 7228,Polaris Australis
274
+ 7235,Okab
275
+ 7254,Meridiana
276
+ 7264,Albaldah
277
+ 7298,Aladfar
278
+ 7310,Altais
279
+ 7337,Arkab Prior
280
+ 7343,Arkab Posterior
281
+ 7348,Rukbat
282
+ 7405,Anser
283
+ 7417,Albireo
284
+ 7462,Alsafi
285
+ 7479,Sham
286
+ 7525,Tarazed
287
+ 7528,Fawaris
288
+ 7557,Altair
289
+ 7595,Libertas
290
+ 7597,Terebellum
291
+ 7602,Alshain
292
+ 7754,Algedi
293
+ 7773,Alshat
294
+ 7776,Dabih
295
+ 7790,Peacock
296
+ 7796,Sadr
297
+ 7852,Aldulfin
298
+ 7882,Rotanev
299
+ 7906,Sualocin
300
+ 7924,Deneb
301
+ 7949,Aljanah
302
+ 7950,Albali
303
+ 8030,Musica
304
+ 8131,Kitalpha
305
+ 8162,Alderamin
306
+ 8232,Sadalsuud
307
+ 8238,Alfirk
308
+ 8264,Bunda
309
+ 8278,Nashira
310
+ 8301,Azelfafage
311
+ 8308,Enif
312
+ 8322,Deneb Algedi
313
+ 8353,Aldhanab
314
+ 8414,Sadalmelik
315
+ 8417,Kurhah
316
+ 8425,Alnair
317
+ 8450,Biham
318
+ 8499,Ancha
319
+ 8518,Sadachbia
320
+ 8610,Situla
321
+ 8634,Homam
322
+ 8636,Tiaki
323
+ 8650,Matar
324
+ 8684,Sadalbari
325
+ 8709,Skat
326
+ 8728,Fomalhaut
327
+ 8729,Helvetios
328
+ 8773,Fumalsamakah
329
+ 8775,Scheat
330
+ 8781,Markab
331
+ 8880,Salm
332
+ 8905,Alkarab
333
+ 8930,Veritate
334
+ 8974,Errai
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Skymap
6
+ module Catalog
7
+ class StarNames
8
+ PATH = File.expand_path("../../../data/star_names.csv", __dir__)
9
+
10
+ def [](hr)
11
+ names[hr]
12
+ end
13
+
14
+ private
15
+
16
+ def names
17
+ @names ||= CSV
18
+ .read(PATH, headers: true, encoding: "UTF-8")
19
+ .to_h { |row| [row["hr"].to_i, row["name"]] }
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/skymap/chart.rb CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Skymap
4
4
  class Chart
5
- def initialize(observer:, instant:, canvas:)
5
+ def initialize(observer:, instant:, canvas:, theme: Theme.dark)
6
6
  @observer = observer
7
7
  @instant = instant
8
8
  @canvas = canvas
9
+ @theme = theme
9
10
  end
10
11
 
11
12
  def render(*layers)
@@ -16,7 +17,7 @@ module Skymap
16
17
  )
17
18
  elements = layers.flat_map { |layer| layer.elements(context) }
18
19
 
19
- Renderer::SVG.new(canvas: @canvas).render(elements)
20
+ Renderer::SVG.new(canvas: @canvas, theme: @theme).render(elements)
20
21
  end
21
22
  end
22
23
  end
@@ -59,16 +59,11 @@ module Skymap
59
59
  name = @names[planet]
60
60
  return unless name
61
61
 
62
- size = Renderer::Caption.size_for(context.canvas)
63
- towards_center = (center.y > context.canvas.center.y) ? -1 : 1
64
-
65
- Renderer::Caption.new(
66
- position: Point.new(
67
- x: center.x,
68
- y: center.y + towards_center * (radius + size)
69
- ),
62
+ Renderer::Caption.beside(
63
+ center: center,
64
+ radius: radius,
65
+ canvas: context.canvas,
70
66
  text: name,
71
- size: size,
72
67
  kind: :planet
73
68
  )
74
69
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skymap
4
+ module Layers
5
+ class StarNames
6
+ DEFAULT_MAGNITUDE_LIMIT = 2
7
+
8
+ def initialize(
9
+ stars:,
10
+ magnitude_limit: DEFAULT_MAGNITUDE_LIMIT,
11
+ stars_magnitude_limit: Stars::DEFAULT_MAGNITUDE_LIMIT,
12
+ names: Catalog::StarNames.new
13
+ )
14
+ @stars = stars
15
+ @magnitude_limit = magnitude_limit
16
+ @stars_magnitude_limit = stars_magnitude_limit
17
+ @names = names
18
+ end
19
+
20
+ def elements(context)
21
+ star_size = StarSize.for(
22
+ canvas: context.canvas,
23
+ magnitude_limit: @stars_magnitude_limit
24
+ )
25
+
26
+ @stars.filter_map do |star|
27
+ next if star.magnitude > @magnitude_limit
28
+
29
+ name = @names[star.hr]
30
+ next unless name
31
+
32
+ next if context.horizontal(star).altitude.negative?
33
+
34
+ Renderer::Caption.beside(
35
+ center: context.position(star),
36
+ radius: star_size.radius(star.magnitude),
37
+ canvas: context.canvas,
38
+ text: name,
39
+ kind: :star
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -3,11 +3,38 @@
3
3
  module Skymap
4
4
  module Renderer
5
5
  CAPTION_SIZE_RATIO = 0.028
6
+ CAPTION_CHARACTER_WIDTH_RATIO = 0.55
6
7
 
7
8
  Caption = Data.define(:position, :text, :size, :kind) do
8
9
  def self.size_for(canvas)
9
10
  CAPTION_SIZE_RATIO * canvas.radius
10
11
  end
12
+
13
+ def self.beside(center:, radius:, canvas:, text:, kind:)
14
+ size = size_for(canvas)
15
+ towards_center = (center.y > canvas.center.y) ? -1 : 1
16
+ y = center.y + towards_center * (radius + size)
17
+
18
+ new(
19
+ position: Point.new(
20
+ x: within_sky(center.x, y, text, size, canvas),
21
+ y: y
22
+ ),
23
+ text: text,
24
+ size: size,
25
+ kind: kind
26
+ )
27
+ end
28
+
29
+ def self.within_sky(x, y, text, size, canvas)
30
+ dy = (y - canvas.center.y).abs + size / 2
31
+ room = Math.sqrt([canvas.radius**2 - dy**2, 0].max)
32
+ half_width = text.length * size * CAPTION_CHARACTER_WIDTH_RATIO / 2
33
+ reach = [room - half_width, 0].max
34
+
35
+ canvas.center.x + (x - canvas.center.x).clamp(-reach, reach)
36
+ end
37
+ private_class_method :within_sky
11
38
  end
12
39
  end
13
40
  end
@@ -5,25 +5,12 @@ require "victor"
5
5
  module Skymap
6
6
  module Renderer
7
7
  class SVG
8
- SKY_COLOR = "#000000"
9
- HORIZON_COLOR = "#3262a8"
10
8
  HORIZON_WIDTH = 3
11
- STAR_COLOR = "#ffffff"
12
- LINE_COLOR = "#46689c"
13
- BOUNDARY_COLOR = "#2b3f5e"
14
9
  BOUNDARY_DASH_RATIO = 4
15
- ECLIPTIC_COLOR = "#8c7a3a"
16
- PLANET_COLOR = "#ffe6b3"
17
- SUN_COLOR = "#ffd34d"
18
- MOON_COLOR = "#f1ecd6"
19
- MOON_SHADOW_COLOR = "#232a35"
20
- MOON_SHADOW_OPACITY = 0.6
21
- CAPTION_COLORS = {constellation: "#6f8fc2", planet: PLANET_COLOR}
22
- LABEL_COLOR = HORIZON_COLOR
23
- LABEL_FONT = "sans-serif"
24
-
25
- def initialize(canvas:)
10
+
11
+ def initialize(canvas:, theme:)
26
12
  @canvas = canvas
13
+ @theme = theme
27
14
  end
28
15
 
29
16
  def render(elements)
@@ -33,12 +20,14 @@ module Skymap
33
20
  height: @canvas.size
34
21
  )
35
22
 
23
+ draw_background(svg)
24
+
36
25
  svg.circle(
37
26
  cx: @canvas.center.x.round(2),
38
27
  cy: @canvas.center.y.round(2),
39
28
  r: @canvas.radius.round(2),
40
- fill: SKY_COLOR,
41
- stroke: HORIZON_COLOR,
29
+ fill: @theme.sky,
30
+ stroke: @theme.horizon,
42
31
  stroke_width: HORIZON_WIDTH
43
32
  )
44
33
 
@@ -62,13 +51,25 @@ module Skymap
62
51
 
63
52
  private
64
53
 
54
+ def draw_background(svg)
55
+ return if @theme.background == "none"
56
+
57
+ svg.rect(
58
+ x: 0,
59
+ y: 0,
60
+ width: @canvas.size,
61
+ height: @canvas.size,
62
+ fill: @theme.background
63
+ )
64
+ end
65
+
65
66
  def draw_line(svg, line)
66
67
  svg.line(
67
68
  x1: line.from.x.round(2),
68
69
  y1: line.from.y.round(2),
69
70
  x2: line.to.x.round(2),
70
71
  y2: line.to.y.round(2),
71
- stroke: LINE_COLOR,
72
+ stroke: @theme.constellation_line,
72
73
  stroke_width: line.width.round(2),
73
74
  stroke_linecap: "round"
74
75
  )
@@ -82,7 +83,7 @@ module Skymap
82
83
  "#{point.x.round(2)},#{point.y.round(2)}"
83
84
  }.join(" "),
84
85
  fill: "none",
85
- stroke: BOUNDARY_COLOR,
86
+ stroke: @theme.boundary,
86
87
  stroke_width: boundary.width.round(2),
87
88
  stroke_dasharray: "#{dash} #{dash}"
88
89
  )
@@ -94,7 +95,7 @@ module Skymap
94
95
  "#{point.x.round(2)},#{point.y.round(2)}"
95
96
  }.join(" "),
96
97
  fill: "none",
97
- stroke: ECLIPTIC_COLOR,
98
+ stroke: @theme.ecliptic,
98
99
  stroke_width: ecliptic.width.round(2),
99
100
  stroke_linejoin: "round"
100
101
  )
@@ -105,7 +106,7 @@ module Skymap
105
106
  cx: dot.center.x.round(2),
106
107
  cy: dot.center.y.round(2),
107
108
  r: dot.radius.round(2),
108
- fill: STAR_COLOR
109
+ fill: @theme.star
109
110
  )
110
111
  end
111
112
 
@@ -114,7 +115,7 @@ module Skymap
114
115
  cx: planet.center.x.round(2),
115
116
  cy: planet.center.y.round(2),
116
117
  r: planet.radius.round(2),
117
- fill: PLANET_COLOR
118
+ fill: @theme.planet
118
119
  )
119
120
  end
120
121
 
@@ -123,7 +124,7 @@ module Skymap
123
124
  cx: sun.center.x.round(2),
124
125
  cy: sun.center.y.round(2),
125
126
  r: sun.radius.round(2),
126
- fill: SUN_COLOR
127
+ fill: @theme.sun
127
128
  )
128
129
  end
129
130
 
@@ -139,26 +140,36 @@ module Skymap
139
140
  lit = "M #{top} A #{radius},#{radius} 0 0 1 #{bottom} " \
140
141
  "A #{terminator},#{radius} 0 0 #{bulge} #{top} Z"
141
142
 
143
+ theme = @theme
142
144
  svg.build do
143
145
  g(transform: "rotate(#{moon.rotation.round(2)} #{x} #{y})") do
144
146
  circle(
145
147
  cx: x,
146
148
  cy: y,
147
149
  r: radius,
148
- fill: MOON_SHADOW_COLOR,
149
- fill_opacity: MOON_SHADOW_OPACITY
150
+ fill: theme.moon_shadow,
151
+ fill_opacity: theme.moon_shadow_opacity.to_f
150
152
  )
151
- path(d: lit, fill: MOON_COLOR)
153
+ path(d: lit, fill: theme.moon)
152
154
  end
153
155
  end
154
156
  end
155
157
 
156
158
  def draw_label(svg, label)
157
- draw_text(svg, label, LABEL_COLOR)
159
+ draw_text(svg, label, @theme.label)
158
160
  end
159
161
 
160
162
  def draw_caption(svg, caption)
161
- draw_text(svg, caption, CAPTION_COLORS.fetch(caption.kind))
163
+ draw_text(svg, caption, caption_color(caption.kind))
164
+ end
165
+
166
+ def caption_color(kind)
167
+ case kind
168
+ when :constellation then @theme.constellation_name
169
+ when :star then @theme.star_name
170
+ when :planet then @theme.planet_name
171
+ else raise ArgumentError, "unknown caption kind: #{kind}"
172
+ end
162
173
  end
163
174
 
164
175
  def draw_text(svg, label, fill)
@@ -167,7 +178,7 @@ module Skymap
167
178
  x: label.position.x.round(2),
168
179
  y: label.position.y.round(2),
169
180
  font_size: label.size.round(2),
170
- font_family: LABEL_FONT,
181
+ font_family: @theme.font,
171
182
  text_anchor: "middle",
172
183
  dominant_baseline: "central",
173
184
  fill: fill
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skymap
4
+ Theme = Data.define(
5
+ :background,
6
+ :sky,
7
+ :horizon,
8
+ :star,
9
+ :constellation_line,
10
+ :boundary,
11
+ :ecliptic,
12
+ :planet,
13
+ :sun,
14
+ :moon,
15
+ :moon_shadow,
16
+ :moon_shadow_opacity,
17
+ :constellation_name,
18
+ :star_name,
19
+ :planet_name,
20
+ :label,
21
+ :font
22
+ ) do
23
+ def self.dark
24
+ new(
25
+ background: "none",
26
+ sky: "#000000",
27
+ horizon: "#3262a8",
28
+ star: "#ffffff",
29
+ constellation_line: "#46689c",
30
+ boundary: "#2b3f5e",
31
+ ecliptic: "#8c7a3a",
32
+ planet: "#ffe6b3",
33
+ sun: "#ffd34d",
34
+ moon: "#f1ecd6",
35
+ moon_shadow: "#232a35",
36
+ moon_shadow_opacity: 0.6,
37
+ constellation_name: "#6f8fc2",
38
+ star_name: "#a8b8d0",
39
+ planet_name: "#ffe6b3",
40
+ label: "#3262a8",
41
+ font: "sans-serif"
42
+ )
43
+ end
44
+
45
+ def self.light
46
+ new(
47
+ background: "#ffffff",
48
+ sky: "#ffffff",
49
+ horizon: "#1f3f73",
50
+ star: "#111111",
51
+ constellation_line: "#5b7bb0",
52
+ boundary: "#b8c4d9",
53
+ ecliptic: "#b08d1f",
54
+ planet: "#c2570c",
55
+ sun: "#f0a500",
56
+ moon: "#e8d27a",
57
+ moon_shadow: "#3b4252",
58
+ moon_shadow_opacity: 0.35,
59
+ constellation_name: "#4a679a",
60
+ star_name: "#3d4a60",
61
+ planet_name: "#c2570c",
62
+ label: "#1f3f73",
63
+ font: "sans-serif"
64
+ )
65
+ end
66
+
67
+ def self.night
68
+ new(
69
+ background: "#000000",
70
+ sky: "#000000",
71
+ horizon: "#7a1010",
72
+ star: "#ff6b6b",
73
+ constellation_line: "#8a2020",
74
+ boundary: "#4a1212",
75
+ ecliptic: "#a33a1a",
76
+ planet: "#ff9a7a",
77
+ sun: "#ff8a3d",
78
+ moon: "#ffb3a0",
79
+ moon_shadow: "#2a0a0a",
80
+ moon_shadow_opacity: 0.6,
81
+ constellation_name: "#c24a4a",
82
+ star_name: "#e08a8a",
83
+ planet_name: "#ff9a7a",
84
+ label: "#7a1010",
85
+ font: "sans-serif"
86
+ )
87
+ end
88
+
89
+ def initialize(**slots)
90
+ slots.except(:moon_shadow_opacity).each do |slot, value|
91
+ check_text(slot, value)
92
+ end
93
+ if slots.key?(:moon_shadow_opacity)
94
+ check_opacity(slots[:moon_shadow_opacity])
95
+ end
96
+
97
+ super
98
+ end
99
+
100
+ private
101
+
102
+ def check_text(slot, value)
103
+ raise ArgumentError, "#{slot} must be a string" unless value.is_a?(String)
104
+ raise ArgumentError, "#{slot} must not be blank" if value.strip.empty?
105
+ end
106
+
107
+ def check_opacity(opacity)
108
+ return if opacity.is_a?(Numeric) && opacity.real? &&
109
+ opacity.between?(0, 1)
110
+
111
+ raise ArgumentError, "moon_shadow_opacity must be between 0 and 1"
112
+ end
113
+ end
114
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Skymap
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/skymap.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "skymap/canvas"
6
6
  require_relative "skymap/catalog/constellation_boundaries"
7
7
  require_relative "skymap/catalog/constellation_lines"
8
8
  require_relative "skymap/catalog/constellation_names"
9
+ require_relative "skymap/catalog/star_names"
9
10
  require_relative "skymap/catalog/stars"
10
11
  require_relative "skymap/chart"
11
12
  require_relative "skymap/constellation_boundary"
@@ -19,6 +20,7 @@ require_relative "skymap/layers/constellation_names"
19
20
  require_relative "skymap/layers/ecliptic"
20
21
  require_relative "skymap/layers/moon"
21
22
  require_relative "skymap/layers/planets"
23
+ require_relative "skymap/layers/star_names"
22
24
  require_relative "skymap/layers/stars"
23
25
  require_relative "skymap/layers/sun"
24
26
  require_relative "skymap/point"
@@ -35,6 +37,7 @@ require_relative "skymap/renderer/sun"
35
37
  require_relative "skymap/renderer/svg"
36
38
  require_relative "skymap/star"
37
39
  require_relative "skymap/star_size"
40
+ require_relative "skymap/theme"
38
41
  require_relative "skymap/version"
39
42
 
40
43
  module Skymap
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémy Hannequin
@@ -148,12 +148,14 @@ files:
148
148
  - data/constellations/iau/boundaries.csv
149
149
  - data/constellations/iau/lines.csv
150
150
  - data/constellations/iau/names.csv
151
+ - data/star_names.csv
151
152
  - data/stars.csv
152
153
  - lib/skymap.rb
153
154
  - lib/skymap/canvas.rb
154
155
  - lib/skymap/catalog/constellation_boundaries.rb
155
156
  - lib/skymap/catalog/constellation_lines.rb
156
157
  - lib/skymap/catalog/constellation_names.rb
158
+ - lib/skymap/catalog/star_names.rb
157
159
  - lib/skymap/catalog/stars.rb
158
160
  - lib/skymap/chart.rb
159
161
  - lib/skymap/constellation_boundary.rb
@@ -167,6 +169,7 @@ files:
167
169
  - lib/skymap/layers/ecliptic.rb
168
170
  - lib/skymap/layers/moon.rb
169
171
  - lib/skymap/layers/planets.rb
172
+ - lib/skymap/layers/star_names.rb
170
173
  - lib/skymap/layers/stars.rb
171
174
  - lib/skymap/layers/sun.rb
172
175
  - lib/skymap/point.rb
@@ -183,6 +186,7 @@ files:
183
186
  - lib/skymap/renderer/svg.rb
184
187
  - lib/skymap/star.rb
185
188
  - lib/skymap/star_size.rb
189
+ - lib/skymap/theme.rb
186
190
  - lib/skymap/version.rb
187
191
  homepage: https://github.com/rhannequin/skymap
188
192
  licenses: