buildstardb.pl
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:18k
源码类别:

OpenGL

开发平台:

Visual C++

  1. #!/usr/bin/perl
  2. # buildstardb.pl
  3. #
  4. # Builds the stars.dat and revised.stc files for Celestia
  5. # For usage instructions, run with the -? command line switch
  6. #
  7. # Version 1.2 - Andrew Tribick (2009-02-01)
  8. # This code makes use of the following datasets:
  9. #
  10. # ESA, 1997, The Hipparcos Catalogue, ESA SP-1200
  11. #   available at http://cdsarc.u-strasbg.fr/viz-bin/Cat?I/239
  12. #
  13. # Hipparcos, the new Reduction of the Raw data
  14. #   Floor van Leeuwen, 2007 "Hipparcos, the New Reduction of the Raw Data"
  15. #   Astrophysics & Space Science Library #350.
  16. #   available at http://cdsarc.u-strasbg.fr/viz-bin/Cat?I/311
  17. use Math::Trig;
  18. use strict;
  19. # default file paths
  20. my $HIP_PATH  = 'hip_main.dat';
  21. my $HIP2_PATH = 'hip2.dat';
  22. my $DAT_PATH  = 'stars.dat';
  23. my $TXT_PATH  = 'stars.txt';
  24. # by default turn spectral type guesser on
  25. my $GUESS_TYPES = 1;
  26. # some physical/astronomical constants
  27. my $LY_PER_PARSEC = 3.26167; # taken from astro.h
  28. my $J2000Obliquity = deg2rad(23.4392911);
  29. my $cc = cos($J2000Obliquity);
  30. my $ss = sin($J2000Obliquity);
  31. my @eqToCel = (
  32. [1,   0,    0],
  33. [0, $cc, -$ss],
  34. [0, $ss,  $cc]
  35. );
  36. # B-V magnitudes and spectral types
  37. # from Lang, K.  "Astrophysical Data: Planets and Stars"(1991)
  38. my %SpBV = (
  39. 'O5' => -0.33,
  40. 'O8' => -0.32,
  41. 'O9' => -0.31,
  42. 'B0' => -0.30,
  43. 'B1' => -0.265,
  44. 'B2' => -0.24,
  45. 'B3' => -0.205,
  46. 'B5' => -0.17,
  47. 'B6' => -0.15,
  48. 'B7' => -0.135,
  49. 'B8' => -0.11,
  50. 'B9' => -0.075,
  51. 'A0' => -0.02,
  52. 'A1' => 0.01,
  53. 'A2' => 0.05,
  54. 'A3' => 0.08,
  55. 'A5' => 0.15,
  56. 'A7' => 0.20,
  57. 'A8' => 0.25,
  58. 'F0' => 0.30,
  59. 'F2' => 0.35,
  60. 'F5' => 0.44,
  61. 'F8' => 0.52,
  62. 'G0' => 0.58,
  63. 'G2' => 0.63,
  64. 'G5' => 0.68,
  65. 'G8' => 0.74,
  66. 'K0' => 0.81,
  67. 'K1' => 0.86,
  68. 'K2' => 0.91,
  69. 'K3' => 0.96,
  70. 'K5' => 1.15,
  71. 'K7' => 1.33,
  72. 'M0' => 1.40,
  73. 'M1' => 1.46,
  74. 'M2' => 1.49,
  75. 'M3' => 1.51,
  76. 'M4' => 1.54,
  77. 'M5' => 1.64,
  78. 'M6' => 1.73,
  79. 'M7' => 1.80,
  80. 'M8' => 1.93
  81. );
  82. # digit meanings for SpectralClass data type
  83. my %SC_StarType = (
  84. 'NormalStar' => 0x0000,
  85. 'WhiteDwarf' => 0x1000,
  86. 'NeutronStar' => 0x2000,
  87. 'BlackHole' => 0x3000,
  88. 'Mask' => 0xf000
  89. );
  90. my %SC_SpecClass = (
  91. 'O' => 0x0000,
  92. 'B' => 0x0100,
  93. 'A' => 0x0200,
  94. 'F' => 0x0300,
  95. 'G' => 0x0400,
  96. 'K' => 0x0500,
  97. 'M' => 0x0600,
  98. 'R' => 0x0700,
  99. 'S' => 0x0800,
  100. 'N' => 0x0900,
  101. 'WC' => 0x0a00,
  102. 'WN' => 0x0b00,
  103. '?' => 0x0c00,
  104. 'L' => 0x0d00,
  105. 'T' => 0x0e00,
  106. 'C' => 0x0f00,
  107. 'DA' => 0x0000,
  108. 'DB' => 0x0100,
  109. 'DC' => 0x0200,
  110. 'DO' => 0x0300,
  111. 'DQ' => 0x0400,
  112. 'DZ' => 0x0500,
  113. 'D' => 0x0600,
  114. 'DX' => 0x0700,
  115. 'Mask' => 0x0f00
  116. );
  117. my %SC_Subclass = (
  118. '0' => 0x0000,
  119. '1' => 0x0010,
  120. '2' => 0x0020,
  121. '3' => 0x0030,
  122. '4' => 0x0040,
  123. '5' => 0x0050,
  124. '6' => 0x0060,
  125. '7' => 0x0070,
  126. '8' => 0x0080,
  127. '9' => 0x0090,
  128. '?' => 0x00a0,
  129. 'Mask' => 0x00f0
  130. );
  131. my %SC_LumClass = (
  132. 'Ia0' => 0x0000,
  133. 'Ia' => 0x0001,
  134. 'Ib' => 0x0002,
  135. 'II' => 0x0003,
  136. 'III' => 0x0004,
  137. 'IV' => 0x0005,
  138. 'V' => 0x0006,
  139. 'VI' => 0x0007,
  140. '?' => 0x0008,
  141. 'Mask' => 0x000f
  142. );
  143. # data stored in these arrays
  144. my %stars = (); # star details
  145. ReadHipparcos();
  146. ReadOldHipparcos();
  147. FixData();
  148. CheckStars();
  149. WriteDat();
  150. # ---------------------------- END OF MAIN PROGRAM --------------------------- #
  151. # --------------------------- INPUT/OUTPUT FUNCTIONS ------------------------- #
  152. # Read the Astrometric Catalogue into associative array
  153. sub ReadHipparcos {
  154. print "Reading Astrometric Catalog...n";
  155. local(*HIPFILE);
  156. if(!open(HIPFILE, '<', $HIP2_PATH)) {
  157. print "  ERROR: Could not open $HIP2_PATHn";
  158. return;
  159. }
  160. my $numStars = 0;
  161. while (my $curLine = <HIPFILE>) {
  162. chomp $curLine;
  163. my $HIP = Trim(substr($curLine, 0, 6));
  164. # note all entries are inserted into list in case subsequent
  165. # processing inserts missing properties.
  166. my %star = (
  167. 'RArad'    => substr($curLine,  15, 13),
  168. 'DErad'    => substr($curLine,  29, 13),
  169. 'Plx'      => substr($curLine,  43,  7),
  170. 'e_RArad'  => substr($curLine,  69,  6),
  171. 'e_DErad'  => substr($curLine,  76,  6),
  172. 'e_Plx'    => substr($curLine,  83,  6),
  173. 'Hpmag'    => substr($curLine, 129,  7),
  174. 'B-V'      => substr($curLine, 152,  6)
  175. );
  176. # strip whitespace from values
  177. foreach my $key (keys %{$stars{$HIP}}) {
  178. $stars{$HIP}{$key} =~ s/s//g;
  179. }
  180. # add data
  181. $stars{$HIP} = {
  182. 'RArad'   => $star{'RArad'},
  183. 'DErad'   => $star{'DErad'},
  184. 'Plx'     => $star{'Plx'},
  185. 'e_RArad' => $star{'e_RArad'},
  186. 'e_DErad' => $star{'e_DErad'},
  187. 'e_Plx'   => $star{'e_Plx'},
  188. 'BTmag'   => '',
  189. 'VTmag'   => '',
  190. 'Hpmag'   => $star{'Hpmag'},
  191. 'B-V'     => $star{'B-V'}
  192. };
  193. $numStars++;
  194. }
  195. close(HIPFILE);
  196. print "  Read a total of $numStars records.n";
  197. }
  198. # Read Hipparcos Main Catalog to get Vmag, BTmag, VTmag, SpType
  199. # which are not present in the new revision
  200. sub ReadOldHipparcos {
  201. print "Reading Hipparcos Main Catalog...n";
  202. local(*HIPFILE);
  203. if(!open(HIPFILE, '<', $HIP_PATH)) {
  204. print "  ERROR: Could not open $HIP_PATHn";
  205. return;
  206. }
  207. my $numStars = 0;
  208. while (my $curLine = <HIPFILE>) {
  209. chomp $curLine;
  210. # check that this is hip_main.dat
  211. die "ERROR: Bad catalog format in $HIP_PATHn" if(substr($curLine, 0, 1) ne 'H');
  212. my $HIP = Trim(substr($curLine, 8, 6));
  213. if (exists $stars{$HIP}) {
  214. # add values into entry
  215. $stars{$HIP}{'Vmag'}   = Trim(substr($curLine,  41,  5));
  216. $stars{$HIP}{'BTmag'}  = Trim(substr($curLine, 217,  6));
  217. $stars{$HIP}{'VTmag'}  = Trim(substr($curLine, 230,  6));
  218. $stars{$HIP}{'SpType'} = Trim(substr($curLine, 435, 12));
  219. # terminate SpType at first space
  220. $stars{$HIP}{'SpType'} =~ m/^([^s]*)/;
  221. $stars{$HIP}{'SpType'} = $1;
  222. }
  223. # increment tally
  224. $numStars++;
  225. }
  226. close(HIPFILE);
  227. print "  Read a total of $numStars records.n";
  228. }
  229. sub WriteDat {
  230. my $numStars = keys %stars;
  231. print "Writing databases...n";
  232. print "  Writing binary database to $DAT_PATHn";
  233. local(*DATFILE);
  234. open(DATFILE, '>', $DAT_PATH) or die "ERROR: Could not write to $DAT_PATHn";
  235. binmode(DATFILE);
  236. print "  Writing text database to $TXT_PATHn";
  237. local(*TXTFILE);
  238. open(TXTFILE, '>', $TXT_PATH) or die "ERROR: Could not write to $TXT_PATHn";
  239. # write file header
  240. print DATFILE pack('a8ccL', 'CELSTARS', 0, 1, $numStars);
  241. print TXTFILE sprintf("%un", $numStars);
  242. # write each star
  243. foreach my $HIP (sort { $a <=> $b } keys %stars) {
  244. my $dist = PlxToDistance($stars{$HIP}{'Plx'});
  245. my $theta = $stars{$HIP}{'RArad'} + pi;
  246. my $phi = $stars{$HIP}{'DErad'} - pi / 2;
  247. my @xyz = (
  248.  $dist * cos($theta) * sin($phi),
  249.  $dist * cos($phi),
  250. -$dist * sin($theta) * sin($phi)
  251. );
  252. my $xc = $eqToCel[0][0] * $xyz[0] + $eqToCel[1][0] * $xyz[1] + $eqToCel[2][0] * $xyz[2];
  253. my $yc = $eqToCel[0][1] * $xyz[0] + $eqToCel[1][1] * $xyz[1] + $eqToCel[2][1] * $xyz[2];
  254. my $zc = $eqToCel[0][2] * $xyz[0] + $eqToCel[1][2] * $xyz[1] + $eqToCel[2][2] * $xyz[2];
  255. my $absMag = AppMagToAbsMag($stars{$HIP}{'Vmag'}, $stars{$HIP}{'Plx'});
  256. my $spType = ParseSpType($stars{$HIP}{'SpType'});
  257. print DATFILE pack('LfffsS', $HIP, $xc, $yc, $zc, $absMag * 256, $spType);
  258. print TXTFILE sprintf("%u  %.9f %+.9f %.6f %.2f %sn", $HIP,
  259.                       rad2deg($stars{$HIP}{'RArad'}), rad2deg($stars{$HIP}{'DErad'}),
  260.   $dist, $stars{$HIP}{'Vmag'}, $stars{$HIP}{'SpType'});
  261. }
  262. close(DATFILE);
  263. close(TXTFILE);
  264. print "  Wrote a total of $numStars stars.n";
  265. }
  266. # -------------------------- DATA HANDLING ROUTINES -------------------------- #
  267. # fix missing data
  268. sub FixData {
  269. print "Fixing data...n";
  270. foreach my $HIP (keys %stars) {
  271. my $Bt = $stars{$HIP}{'BTmag'};
  272. my $Vt = $stars{$HIP}{'VTmag'};
  273. my $Hpmag = $stars{$HIP}{'Hpmag'};
  274. my $BtVt = '';
  275. my $BtVt = $Bt - $Vt if(($Bt ne '') && ($Vt ne ''));
  276. # if Vmag missing, calculate from Bt and Vt magnitudes or Hpmag
  277. if (($stars{$HIP}{'Vmag'} eq '') && ($Vt ne '')) {
  278. if ($BtVt eq '') {
  279. $stars{$HIP}{'Vmag'} = VtToVmag($Vt, 0);
  280. } else {
  281. $stars{$HIP}{'Vmag'} = VtToVmag($Vt, $BtVt);
  282. }
  283. } elsif (($stars{$HIP}{'Vmag'} eq '') && ($Hpmag ne '')) {
  284. $stars{$HIP}{'Vmag'} = HpToVmag($Hpmag, 0);
  285. }
  286. # if B-V missing, calculate from Bt and Vt magnitudes
  287. $stars{$HIP}{'B-V'} = BtVtToBV($BtVt) if(($stars{$HIP}{'B-V'} eq '') && ($BtVt ne ''));
  288. # if star has unknown spectral type, attempt to guess from B-V
  289. if ((SpTypeToString(ParseSpType($stars{$HIP}{'SpType'})) eq '?') && ($GUESS_TYPES == 1)) {
  290. $stars{$HIP}{'SpType'} = GuessSpType($stars{$HIP}{'B-V'}) if($stars{$HIP}{'B-V'} ne '');
  291. }
  292. }
  293. print "  Fixed.n";
  294. }
  295. # drop stars with bad data
  296. sub CheckStars {
  297. print "Checking data...n";
  298. my $good = 0;
  299. my $dubious = 0;
  300. my $dropped = 0;
  301. my $brightdrop = 0;
  302. foreach my $HIP (keys %stars) {
  303. my $badness = TestDubious($stars{$HIP});
  304. if ($badness == 0) {
  305. # good stars are fine
  306. $good++;
  307. } else {
  308. # drop star
  309. $brightdrop++ if(($stars{$HIP}{'Vmag'} ne '') && ($stars{$HIP}{'Vmag'} <= 6)); delete $stars{$HIP};
  310. $dropped++;
  311. }
  312. }
  313. print "  $good stars with good data included.n";
  314. print "  $dropped stars dropped, of which $brightdrop are bright stars.n";
  315. }
  316. # reject stars 
  317. sub TestDubious {
  318. my $star = shift;
  319. my $dubious = 0;
  320. # if there is no magnitude information, we can't use this star
  321. $dubious = 1 if($star->{'Vmag'} eq '');
  322. # if low, negative or missing parallax, reject
  323. $dubious = 1 if(($star->{'Plx'} eq '') || ($star->{'Plx'} < 0.2));
  324. # if parallax error >= parallax, reject $dubious = 1 if($star->{'Plx'} <= $star->{'e_Plx'});
  325. # if no position information, reject
  326. $dubious = 1 if(($star->{'RArad'} eq '') || ($star->{'DErad'} eq ''));
  327. # if large error in position, reject my $e_RADec = sqrt($star->{'e_RArad'} ** 2 + $star->{'e_DErad'} ** 2);
  328. $dubious = 1 if($e_RADec > 25); # otherwise the star is fine
  329. return $dubious;
  330. }
  331. # ------------------------ ASTROPHYSICAL CALCULATIONS ------------------------ #
  332. # convert apparent magnitude to absolute magnitude using parallax
  333. sub AppMagToAbsMag {
  334. my $appMag = shift;
  335. my $plx = shift;
  336. return $appMag - 5 * Log10(100 / $plx);
  337. }
  338. # convert parallax to distance in light years
  339. sub PlxToDistance {
  340. my $plx = shift;
  341. return 1000/$plx * $LY_PER_PARSEC;
  342. }
  343. # --------------------- MAGNITUDE SYSTEM TRANSFORMATIONS --------------------- #
  344. # convert Vt magnitude to Vmag
  345. # from Mamajek, Meyer & Liebert (2002), AJ 124 (3), 1670-1694
  346. sub VtToVmag {
  347. my $Vt = shift;
  348. my $BtVt = shift;
  349. return $Vt + 9.7e-04 - 1.334e-01 * $BtVt + 5.486e-02 * $BtVt * $BtVt - 1.998e-02 * $BtVt * $BtVt * $BtVt;
  350. }
  351. # convert Hp magnitude to Vmag
  352. # based on cubic polynomial fit to data in Bessel, M.S (2000), PASP 112, 961-965
  353. sub HpToVmag {
  354. my $Hp = shift;
  355. my $BtVt = shift;
  356. return $Hp - 7.967e-03 - 2.537e-01 * $BtVt + 1.073e-01 * $BtVt * $BtVt - 2.678e-03 * $BtVt * $BtVt * $BtVt;
  357. }
  358. # convert Bt-Vt to B-V
  359. # from Mamajek, Meyer & Liebert (2002), AJ 124 (3), 1670-1694
  360. sub BtVtToBV {
  361. my $BtVt = shift;
  362. my $BV = $BtVt - 7.813e-03 * $BtVt - 1.489e-01 * $BtVt * $BtVt + 3.384e-02 * $BtVt * $BtVt * $BtVt;
  363. return $BV;
  364. }
  365. # ------------------------- SPECTRAL CLASS HANDLING -------------------------- #
  366. # Implements the stellar class parser from stellarclass.cpp
  367. sub ParseSpType {
  368. my $st = shift;
  369. $st =~ s/s//g;
  370. $st = '?' if($st eq '');
  371. my $i = 0;
  372. my $state = 'BeginState';
  373. my $starType = $SC_StarType{'NormalStar'};
  374. my $specClass = $SC_SpecClass{'?'};
  375. my $subclass = $SC_Subclass{'?'};
  376. my $lumClass = $SC_LumClass{'?'};
  377. while ($state ne 'EndState') {
  378. my $c = ($i < length($st)) ? substr($st, $i, 1) : '';
  379. if ($state eq 'BeginState') {
  380. if ($c eq 'Q') {
  381. $starType = $SC_StarType{'NeutronStar'};
  382. $state = 'EndState';
  383. } elsif ($c eq 'X') {
  384. $starType = $SC_StarType{'BlackHole'};
  385. $state = 'EndState';
  386. } elsif ($c eq 'D') {
  387. $starType = $SC_StarType{'WhiteDwarf'};
  388. $specClass = $SC_SpecClass{'D'};
  389. $state = 'WDTypeState';
  390. $i++;
  391. } elsif ($c eq 's') {
  392. $state = 'SubdwarfPrefixState';
  393. $i++;
  394. } elsif ($c eq '?') {
  395. $state = 'EndState';
  396. } else {
  397. $state = 'NormalStarClassState';
  398. }
  399. } elsif ($state eq 'WolfRayetTypeState') {
  400. if ($c =~ m/[CN]/) {
  401. $specClass = $SC_SpecClass{'W'.$c};
  402. $state = 'NormalStarSubclassState';
  403. $i++;
  404. } else {
  405. $specClass = $SC_SpecClass{'WC'};
  406. $state = 'NormalStarSubclassState';
  407. $i++;
  408. }
  409. } elsif ($state eq 'SubdwarfPrefixState') {
  410. if ($c eq 'd') {
  411. $lumClass = $SC_LumClass{'VI'};
  412. $state = 'NormalStarClassState';
  413. $i++;
  414. } else {
  415. $state = 'EndState';
  416. }
  417. } elsif ($state eq 'NormalStarClassState') {
  418. if ($c eq 'W') {
  419. $state = 'WolfRayetTypeState';
  420. } elsif ($c =~ m/[OBAFGKMRSNLTC]/) {
  421. $specClass = $SC_SpecClass{$c};
  422. $state = 'NormalStarSubclassState';
  423. } else {
  424. $state = 'EndState';
  425. }
  426. $i++;
  427. } elsif ($state eq 'NormalStarSubclassState') {
  428. if ($c =~ m/[0-9]/) {
  429. $subclass = $SC_Subclass{$c};
  430. $state = 'NormalStarSubclassDecimalState';
  431. $i++;
  432. } else {
  433. $state = 'LumClassBeginState';
  434. }
  435. } elsif ($state eq 'NormalStarSubclassDecimalState') {
  436. if ($c eq '.') {
  437. $state = 'NormalStarSubclassFinalState';
  438. $i++;
  439. } else {
  440. $state = 'LumClassBeginState';
  441. }
  442. } elsif ($state eq 'NormalStarSubclassFinalState') {
  443. if ($c =~ m/[0-9]/) {
  444. $state = 'LumClassBeginState';
  445. } else {
  446. $state = 'EndState';
  447. }
  448. $i++;
  449. } elsif ($state eq 'LumClassBeginState') {
  450. if ($c eq 'I') {
  451. $state = 'LumClassIState';
  452. } elsif ($c eq 'V') {
  453. $state = 'LumClassVState';
  454. } else {
  455. $state = 'EndState';
  456. }
  457. $i++;
  458. } elsif ($state eq 'LumClassIState') {
  459. if ($c eq 'I') {
  460. $state = 'LumClassIIState';
  461. } elsif ($c eq 'V') {
  462. $lumClass = $SC_LumClass{'IV'};
  463. $state = 'EndState';
  464. } elsif ($c eq 'a') {
  465. $state = 'LumClassIaState';
  466. } elsif ($c eq 'b') {
  467. $lumClass = $SC_LumClass{'Ib'};
  468. $state = 'EndState';
  469. } elsif ($c eq '-') {
  470. $state = 'LumClassIdashState';
  471. } else {
  472. $lumClass = $SC_LumClass{'Ib'};
  473. $state = 'EndState';
  474. }
  475. $i++;
  476. } elsif ($state eq 'LumClassIIState') {
  477. if ($c eq 'I') {
  478. $lumClass = $SC_LumClass{'III'};
  479. $state = 'EndState';
  480. } else {
  481. $lumClass = $SC_LumClass{'II'};
  482. $state = 'EndState';
  483. }
  484. } elsif ($state eq 'LumClassIdashState') {
  485. if ($c eq 'a') {
  486. $state = 'LumClassIaState';
  487. } elsif ($c eq 'b') {
  488. $lumClass = $SC_LumClass{'Ib'};
  489. $state = 'EndState';
  490. } else {
  491. $lumClass = $SC_LumClass{'Ia'};
  492. $state = 'EndState';
  493. }
  494. } elsif ($state eq 'LumClassIaState') {
  495. if ($c eq '0') {
  496. $lumClass = $SC_LumClass{'Ia0'};
  497. $state = 'EndState';
  498. } else {
  499. $lumClass = $SC_LumClass{'Ia'};
  500. $state = 'EndState';
  501. }
  502. } elsif ($state eq 'LumClassVState') {
  503. if ($c eq 'I') {
  504. $lumClass = $SC_LumClass{'VI'};
  505. $state = 'EndState';
  506. } else {
  507. $lumClass = $SC_LumClass{'V'};
  508. $state = 'EndState';
  509. }
  510. } elsif ($state eq 'WDTypeState') {
  511. if ($c =~ m/[ABCOQXZ]/) {
  512. $specClass = $SC_SpecClass{'D'.$c};
  513. $i++;
  514. } else {
  515. $specClass = $SC_SpecClass{'D'};
  516. }
  517. $state = 'WDExtendedTypeState';
  518. } elsif ($state eq 'WDExtendedTypeState') {
  519. if ($c =~ m/[ABCOQZXVPHE]/) {
  520. $i++;
  521. } else {
  522. $state = 'WDSubclassState';
  523. }
  524. } elsif ($state eq 'WDSubclassState') {
  525. if ($c =~ m/[0-9]/) {
  526. $subclass = $SC_Subclass{$c};
  527. $i++;
  528. }
  529. $state = 'EndState';
  530. } else {
  531. die "ERROR: Unknown state in spectral class parsern";
  532. }
  533. }
  534. return $starType + $specClass + $subclass + $lumClass;
  535. }
  536. # Convert spectral class code to string
  537. sub SpTypeToString {
  538. my $spType = shift;
  539. my $st = '?';
  540. if (($spType & $SC_StarType{'Mask'}) == $SC_StarType{'NormalStar'}) {
  541. foreach my $sp (keys %SC_SpecClass) {
  542. if (($sp !~ /^D/) && ($sp ne 'Mask')) {
  543. $st = $sp if(($spType & $SC_SpecClass{'Mask'}) == $SC_SpecClass{$sp});
  544. }
  545. }
  546. if ($st ne '?') {
  547. foreach my $sc (keys %SC_Subclass) {
  548. if ($sc ne 'Mask') {
  549. if (($spType & $SC_Subclass{'Mask'}) == $SC_Subclass{$sc}) {
  550. $st .= $sc if($sc ne '?');
  551. }
  552. }
  553. }
  554. foreach my $lc (keys %SC_LumClass) {
  555. if ($lc ne 'Mask') {
  556. if (($spType & $SC_LumClass{'Mask'}) == $SC_LumClass{$lc}) {
  557. $st .= $lc if($lc ne '?');
  558. }
  559. }
  560. }
  561. }
  562. } elsif (($spType & $SC_StarType{'Mask'}) == $SC_StarType{'WhiteDwarf'}) {
  563. foreach my $wt (keys %SC_SpecClass) {
  564. if ($wt =~ m/^D/) {
  565. $st = $wt if(($spType & $SC_SpecClass{'Mask'}) == $SC_SpecClass{$wt});
  566. }
  567. }
  568. if ($st ne '?') {
  569. foreach my $sc (keys %SC_Subclass) {
  570. if ($sc ne 'Mask') {
  571. if(($spType & $SC_Subclass{'Mask'}) == $SC_Subclass{$sc}) {
  572. $st .= $sc if($sc ne '?');
  573. }
  574. }
  575. }
  576. }
  577. } elsif (($spType & $SC_StarType{'Mask'}) == $SC_StarType{'NeutronStar'}) {
  578. $st = 'Q';
  579. } elsif (($spType & $SC_StarType{'Mask'}) == $SC_StarType{'BlackHole'}) {
  580. $st = 'X';
  581. }
  582. return $st;
  583. }
  584. # Guess the spectral type from the B-V colour index - use closest match to table
  585. sub GuessSpType {
  586. my $BV = shift;
  587. my $st = '?';
  588. my $minDelta = 9999;
  589. foreach my $trial_st (keys %SpBV) {
  590. if (abs($BV - $SpBV{$trial_st}) < $minDelta) {
  591. $st = $trial_st;
  592. $minDelta = abs($BV - $SpBV{$trial_st});
  593. }
  594. }
  595. return $st;
  596. }
  597. # ---------------- STRING HANDLING AND MATHEMATICAL FUNCTIONS ---------------- #
  598. # remove leading and trailing spaces from a string
  599. sub Trim {
  600. my $st = shift;
  601. $st =~ s/(^s+)|(s+$)//g;
  602. return $st;
  603. }
  604. # calculate log base 10 of a number
  605. sub Log10 {
  606. my $n = shift;
  607. return log($n)/log(10);
  608. }