1 // StarPlot - A program for interactively viewing 3D maps of stellar positions.
2 // Copyright (C) 2000  Kevin B. McCarty
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 /*
19   convert.cc
20   Contains the main() function for the starconvert utility and all the
21   functions allowing it to convert a star data file to StarPlot format.
22 */
23 
24 #include "convert.h"
25 #include <iostream>
26 
27 using std::istream;
28 using std::ostream;
29 using std::string;
30 using std::cin;
31 using std::cout;
32 using std::cerr;
33 using std::endl;
34 
35 // defined in names.cc:
36 extern void get_names(const string &, const namedata &,
37 		      StringList *, StringList *);
38 
39 // defined below:
40 Star * parse_star_record(const string &, const parsedata &);
41 void get_coordinates(const string &, const coordinates &, StringList *);
42 void get_mag_distance(const string &, const string &, const characteristics &,
43 		      double *, double *);
44 void check_for_double_stars(const string &, const systems &, const comments &,
45 			    Star *, Star *);
46 
parse_star_file(istream & infile,ostream & outfile,const parsedata & format,bool using_stdout,bool add_sun)47 void parse_star_file(istream & infile, ostream & outfile,
48 		     const parsedata & format,
49 		     bool using_stdout, bool add_sun)
50 {
51   int lineno = 0;
52   bool sun_found = false, moredata = true;
53   char record[1000];
54   Star * tempstar = 0, * currentstar = 0, * previousstar = 0;
55 
56   // boring legal disclaimer:
57   outfile << starstrings::ssprintf(_(
58 "This StarPlot data file was automatically generated by starconvert %s\n\
59 If this file was derived from a copyrighted source, you may not redistribute\n\
60 it without the permission of the original author.\n\n"), STARPLOT_VERSION)
61 	  << _(
62 "If you obtained this data file by installing one of the data packages\n\
63 available on the StarPlot web page, you should see the file copyright notice\n\
64 in that package for more information.") << endl << endl;
65 
66   do {
67     moredata = (bool)infile.getline(record, 999, '\n');
68     record[999] = 0;
69 
70     // $ ; and , have special meanings to StarPlot, so purge them:
71     for (unsigned int i = 0; i < strlen(record); i++)
72       if (record[i] == '$' || record[i] == ';' || record[i] == ',')
73         record[i] = ' ';
74 
75     tempstar = parse_star_record(record, format);
76     if (tempstar || !moredata) {
77       delete previousstar;
78       previousstar = currentstar;
79       currentstar = tempstar;
80     }
81     else continue;
82 
83     if (previousstar) {
84       Rules r;
85       r.CelestialCoords = true;
86       check_for_double_stars(record, format.Systems, format.Comments,
87 			     previousstar, currentstar);
88       StringList starinfo = previousstar->GetInfo(r, false, ',');
89 
90       // write star information to output
91       outfile << "$ " << starinfo[1];
92       if (starinfo.size() >= 11) {
93 	for (unsigned int i = 10; i < starinfo.size(); i++)
94 	  outfile << ", " << starinfo[i];
95       }
96       outfile << ";" << endl << "  ";
97 
98       outfile << starinfo[2] << "; " << starinfo[3] << "; " << starinfo[4]
99 	      << "; 0;" << endl << "  " << starinfo[5] << "; ";
100       outfile << (starstrings::uppercase(starinfo[1]) == "SUN" ?
101 		  (sun_found = true, "4.85") : starinfo[6]);
102       outfile << "; " << starinfo[9] << "; " << starinfo[7] << ";" << endl;
103 
104       if (starinfo[8].size())
105 	outfile << "  " << starinfo[8] << endl;
106       outfile << endl;
107     }
108     record[0] = 0;
109 
110     lineno++;
111     if (!using_stdout && !(lineno % 1000))
112       cout << starstrings::ssprintf(_("%d records converted."), lineno) << endl;
113 
114   } while (moredata) ;
115 
116   delete previousstar;
117 
118   // if the Sun is not in the data file, append it at the end!
119   if (add_sun && !sun_found)
120     outfile << "$ Sun, Sol;" << endl << "  ; ; 0; 0;" << endl
121     	    << "  G2 V; 4.85; 0; ;" << endl << endl;
122   return;
123 }
124 
125 
126 // parse_star_record(): converts the current record to a Star
127 
parse_star_record(const string & record,const parsedata & format)128 Star * parse_star_record(const string &record, const parsedata & format)
129 {
130   if (starstrings::isempty(record)) return 0;
131 
132   double starmag, stardist;
133   string spectrum;
134   StringList starnames, starcoord, starcomments, starmembership;
135   Star * result = 0;
136 
137   // find the first non-empty spectral class datum out of those specified
138   //  in the specification file
139   unsigned int i = 0;
140   do {
141     if (format.Charact.s[i].start < record.size()) {
142       spectrum = record.substr(format.Charact.s[i].start,
143 			       format.Charact.s[i].len);
144       starstrings::stripspace(spectrum);
145     }
146     i++;
147   } while (i < format.Charact.s.size() &&
148 	   starstrings::isempty(spectrum)) ;
149 
150   // convert comments (if any) to a StringList
151   if (format.Comments.s.len > 0 && format.Comments.s.start < record.size()) {
152     string commentstring = record.substr(format.Comments.s.start,
153 		    			 format.Comments.s.len);
154     starcomments = StringList(commentstring, ' ');
155     starcomments.eraseempty();
156   }
157 
158   get_coordinates(record, format.Coord, &starcoord);
159   get_mag_distance(record, spectrum, format.Charact, &stardist, &starmag);
160   get_names(record, format.Names, &starnames, &starcomments);
161 
162   if (starnames.size() && (starstrings::uppercase(starnames[0]) == "SUN"))
163     stardist = 0.0;
164 
165   if (stardist >= 0.0) {
166     std::ostringstream starstring;
167 
168     citerate_until (StringList, starnames, strptr, -1)
169       starstring << *strptr << ", ";
170     if (starnames.size())
171       starstring << starnames[starnames.size() - 1];
172     else
173       starstring << _("<no name>");
174     starstring << "; ";
175 
176     for (unsigned int i = 0; i < 6; i++)
177       starstring << starcoord[i] << (((i + 1) % 3) ? ", " : "; ");
178     starstring << stardist << "; 0; " << spectrum << "; "
179 	       << starmag << "; " << "0; ; ";
180 
181     citerate (StringList, starcomments, strptr)
182       starstring << *strptr << " ";
183 
184     result = new Star(starstring.str(),
185 		  false /* no fast conversion */,
186 		  false /* no name translation */);
187   }
188 
189   else {
190     cerr << starstrings::ssprintf(_(
191 	"*** Cannot obtain distance to star %s; leaving it out."),
192 	starnames[0].c_str()) << endl;
193   }
194 
195   return result;
196 }
197 
198 
get_coordinates(const string & record,const coordinates & c,StringList * coordstrings)199 void get_coordinates(const string & record, const coordinates & c,
200 		     StringList * coordstrings)
201 {
202   coordstrings->clear();
203 
204   for (unsigned int i = 0; i < NUM_COORD_OPTIONS; i++) {
205     if (c.s[i].len > 0 && c.s[i].start < record.size())
206       coordstrings->push_back(record.substr(c.s[i].start, c.s[i].len));
207     else
208       coordstrings->push_back(string(""));
209   }
210   coordstrings->stripspace();
211 
212   // fourth element is the sign, so concat it with fifth element and erase
213   (*coordstrings)[3] += (*coordstrings)[4];
214   coordstrings->erase(coordstrings->begin() + 4);
215 
216   if (!c.isCelestial) {
217     // in this case we convert from Galactic coordinates so that contents
218     // of output file are in celestial coordinates
219     double theta, phi;
220     SolidAngle omega;
221 
222     phi = starstrings::strs_to_ra((*coordstrings)[0], (*coordstrings)[1],
223 		  		  (*coordstrings)[2], GALACTIC);
224     theta = starstrings::strs_to_dec((*coordstrings)[3], (*coordstrings)[4],
225 				     (*coordstrings)[5]);
226     omega = SolidAngle(phi, theta).toCelestial();
227     coordstrings->clear();
228     coordstrings->push_back(starstrings::ra_to_strs(omega.getPhi()));
229     coordstrings->push_back(starstrings::dec_to_strs(omega.getTheta()));
230   }
231   return;
232 }
233 
234 
235 // get_mag_distance(): This function obtains the distance and magnitude of
236 //  the star.  It goes through the distance/magnitude formatting pairs
237 //  specified in the config file, and uses the first pair which gives an
238 //  acceptable result.
239 
get_mag_distance(const string & record,const string & spectrum,const characteristics & c,double * dist,double * mag)240 void get_mag_distance(const string &record, const string &spectrum,
241 		      const characteristics & c, double *dist, double *mag)
242 {
243   double tempmag, tempdist;
244 
245   for (unsigned int i = 0; i < c.distarray.size(); i++) {
246     if (c.magarray[i].s.len <= 0 || c.magarray[i].s.start >= record.size())
247       continue;
248     char *endptr;
249     string magstring = record.substr(c.magarray[i].s.start,
250 				     c.magarray[i].s.len);
251     starstrings::stripspace(magstring);
252     tempmag = std::strtod(magstring.c_str(), &endptr);
253     if (starstrings::isempty(magstring) || (*endptr != 0 && *endptr != '.'))
254       continue;
255 
256     if (c.distarray[i].type == SPECCLASS) {
257       if (starstrings::isempty(spectrum) || c.magarray[i].type == ABSOLUTE)
258 	continue;
259       SpecClass sclass = SpecClass(spectrum);
260       sclass.initialize();
261       double absmag = sclass.absmag();
262       if (absmag < -25 || absmag > 25) continue;
263 
264       // Technically this should also account for the effect of
265       //  interstellar dust on the visual magnitude...
266       tempdist = starmath::get_distance(tempmag, absmag);
267 
268       // should NOT be using spectral class distance estimate for nearby stars:
269       if (tempdist < 20 /* LY */) continue;
270 
271       *dist = starmath::sigdigits(tempdist, 3);
272       *mag = absmag;
273       return;
274     }
275 
276     else if (c.distarray[i].s.len > 0 &&
277 	     c.distarray[i].s.start < record.size()) {
278       string diststring = record.substr(c.distarray[i].s.start,
279 		      			c.distarray[i].s.len);
280       starstrings::stripspace(diststring);
281       tempdist = std::strtod(diststring.c_str(), &endptr);
282       if (starstrings::isempty(diststring) || (*endptr != 0 && *endptr != '.'))
283 	continue;
284 
285       double parallaxerr = 0.0;
286       if ((c.distarray[i].type == ARCSEC || c.distarray[i].type == MILLIARCSEC)
287 	  && c.distarray[i].err.len > 0
288 	  && c.distarray[i].err.start < record.size()) {
289 	string errstring = record.substr(c.distarray[i].err.start,
290 					 c.distarray[i].err.len);
291 	starstrings::stripspace(errstring);
292 	parallaxerr = std::strtod(errstring.c_str(), &endptr);
293 	if (starstrings::isempty(errstring) || parallaxerr < 0
294 	    || (*endptr != 0 && *endptr != '.'))
295 	  continue;
296       }
297 
298       switch (c.distarray[i].type) {
299       case MILLIARCSEC: tempdist /= 1000.0; parallaxerr /= 1000.0;
300       case ARCSEC:
301 	{
302 	  // if parallax is < min. acceptable parallax, go to next rule
303 	  if (tempdist < c.distarray[i].minparallax) break;
304 	  // if relative error is > max. acceptable error, go to next rule
305 	  else if (c.distarray[i].err.len > 0
306 		   && parallaxerr > c.distarray[i].maxerror * tempdist) break;
307 	  else tempdist = 1.0 / tempdist;
308 	}
309       case PC:          tempdist *= LY_PER_PARSEC;
310       case LY:
311 	{
312 	  *dist = starmath::sigdigits(tempdist, 3);
313 	  if (c.magarray[i].type == ABSOLUTE)
314 	    *mag = tempmag;
315 	  else {
316 	    tempmag = starmath::get_absmag(tempmag, tempdist);
317 	    *mag = starmath::roundoff(tempmag, 1);
318 	  }
319 	  return;
320 	}
321       case SPECCLASS: break; // do nothing
322       } // end switch statement
323     }
324   } // end for loop
325 
326   // if we haven't returned yet, something is wrong; flag the values.
327   *mag = *dist = -9999;
328   return;
329 }
330 
331 
332 // Check for double systems.  This could certainly be improved but it
333 // may not be worth the effort.
334 
check_for_double_stars(const string & record,const systems & s,const comments & c,Star * previous,Star * current)335 void check_for_double_stars(const string &record, const systems & s,
336 			    const comments & c,
337 			    Star *previous, Star *current)
338 {
339   if (!previous || !current) return;
340 
341   // Check for component of multiple star system and save in the "sPlace"
342   // variable of the current Star.
343   if (s.comp.len > 0 && s.comp.start < record.size()) {
344     string component = record.substr(s.comp.start, s.comp.len);
345     starstrings::stripspace(component);
346     current->SetPlace(component[0]);
347   }
348 
349   // Check for separation of multiple star system components.
350   if (current->GetPlace() != 'A') {
351     double separation = 0.0;
352     string sepstr;
353     if (s.isSeparationCommented && c.s.len > 0 && c.s.start < record.size()) {
354       string comments = record.substr(c.s.start, c.s.len);
355       citerate (StringList, s.sep_prefixes, prefix_ptr) {
356 	size_t posn = comments.find(*prefix_ptr);
357 	if (posn < comments.size()) {
358 	  sepstr = comments.substr(posn+(*prefix_ptr).size(), comments.size());
359 	  starstrings::stripspace(sepstr);
360 	  break;
361 	}
362       }
363     }
364     else if (s.sep.len > 0 && s.sep.start < record.size())
365       sepstr = record.substr(s.sep.start, s.sep.len);
366     starstrings::stripspace(sepstr);
367 
368     separation = starmath::atof(sepstr);
369     if (sepstr.size() && sepstr[sepstr.size() - 1] == '\'')
370       separation *= 60;
371 
372     current->SetStarPrimaryDistance(separation * RAD_PER_DEGREE / 3600.0
373 				    * current->GetStarDistance());
374   }
375 
376   // Assume that two stars that are adjacent in raw data file and separated
377   // by less than 1/5 LY are paired or part of a multiple star system.
378   double distdiff = (previous->GetStarXYZ()-current->GetStarXYZ()).magnitude();
379   if (distdiff < 0.2 /* light-years */) {
380     Star *brighter, *dimmer;
381 
382     if (previous->GetPlace() && current->GetPlace())
383       brighter = (previous->GetPlace() > current->GetPlace()) ?
384 		 current : previous;
385     else
386       brighter = (previous->GetStarMagnitude() > current->GetStarMagnitude()) ?
387 		 current : previous;
388     dimmer = (brighter == previous) ? current : previous;
389 
390     if (previous->GetStarMembership().size() == 0) {
391       previous->SetStarMembership(brighter->GetStarNames()[0]);
392       current->SetStarMembership(brighter->GetStarNames()[0]);
393     }
394     else { // at least 3 stars; we are stuck with whatever was decided upon
395            // as the system name when looking at the first pair
396       current->SetStarMembership(previous->GetStarMembership()[0]);
397     }
398     if (dimmer->GetStarPrimaryDistance() == 0.0)
399       dimmer->SetStarPrimaryDistance(distdiff);
400     if (dimmer->GetStarPrimaryDistance() == 0.0)
401       // flag primary distance as small but unknown:
402       dimmer->SetStarPrimaryDistance(-1.0);
403   }
404 }
405 
406 
printusage()407 void printusage()
408 {
409   cout << PROGNAME << " " << _("usage:") << endl
410        << PROGNAME << " " << _("specfile infile [outfile]") << endl << _(
411 "Uses specifications in `specfile' to convert `infile' to StarPlot\n\
412 data format and writes the results to `outfile' if specified, or\n\
413 standard output if not.  The first two arguments are required.\n\
414 Exactly ONE may be replaced with a dash for reading from stdin.\n") << endl;
415   cout << PROGNAME << ": " << _("version") << " " << STARPLOT_VERSION
416        << " (C) 2000-2008 Kevin B. McCarty" << endl
417        << starstrings::ssprintf(_(
418 "%s comes with ABSOLUTELY NO WARRANTY; for details, see\n\
419 the file %s."), PROGNAME, DOCDIR "/COPYING") << endl;
420   return;
421 }
422 
423 
424 // main(): This doesn't do much besides error checking, and calling the two
425 //  top-level parsing functions (one for the specification file, and one for
426 //  the data file).
427 
main(int argc,char ** argv)428 int main(int argc, char **argv)
429 {
430   parsedata specdata;
431   std::ifstream infile, specfile;
432   std::ofstream outfile;
433 
434   // Set up i18n
435   setlocale (LC_ALL, "");
436   setlocale (LC_NUMERIC, "C"); // avoid problems with comma as decimal point
437   bindtextdomain (PACKAGE, LOCALEDIR);
438   textdomain (PACKAGE);
439 
440   // Default values for user-settable options
441   bool add_sun = true;
442 
443   // Parse command-line options
444   int idx = 1;
445   while (idx < argc && argv[idx][0] == '-') {
446     if (strlen(argv[idx]) == 1) {
447       // passed-in argument is "-" representing to use stdin for the specfile,
448       // and we have reached the first non-flag argument
449       break;
450     }
451     else if (strcmp(argv[idx], "--") == 0) {
452       idx++; break;
453     }
454     else if (strcmp(argv[idx], "--help") == 0 || strcmp(argv[idx], "-h") == 0) {
455       printusage(); return 0;
456     }
457     else if (strcmp(argv[idx], "--add-sun") == 0) {
458       add_sun = true;
459     }
460     else if (strcmp(argv[idx], "--no-add-sun") == 0) {
461       add_sun = false;
462     }
463     // catch bad options
464     else {
465       cerr << starstrings::ssprintf(_("%s: Bad option: %s"), argv[0], argv[idx])
466 	   << endl << endl;
467       printusage();
468       return EXIT_FAILURE;
469     }
470 
471     idx++;
472   }
473 
474   // Set main program arguments to specific values after option parsing.
475   // idx is now the position of the first non-flag argument.
476   int nflags = idx - 1;
477   if (nflags) {
478     for (int i = 1; i + nflags < argc; i++)
479       argv[i] = argv[i + nflags];
480     argc -= nflags;
481   }
482 
483   // At this point, argv[1] should be the specfile, argv[2] the input file,
484   // and argv[3] (optional) the output file.
485   if (argc != 3 && argc != 4) {
486     printusage();
487     return EXIT_FAILURE;
488   }
489 
490   if (strcmp(argv[1], "-") == 0) {
491     if (strcmp(argv[2], "-") == 0) {
492       printusage();
493       return EXIT_FAILURE;
494     }
495     else {
496       if (! cin.good()) {
497 	cerr << _("Can't read specifications from stdin - not a tty?") << endl;
498 	return EXIT_FAILURE;
499       }
500       parse_config_file(cin, &specdata);
501     }
502   }
503   else {
504     specfile.open(argv[1]);
505     if (! specfile.good()) {
506       cerr << starstrings::ssprintf(_(
507 	"Can't open specification file `%s' for input; exiting."), argv[1])
508 	   << endl;
509       return EXIT_FAILURE;
510     }
511     parse_config_file(specfile, &specdata);
512     specfile.close();
513   }
514 
515   if (argc == 4) {
516     outfile.open(argv[3]);
517     if (! outfile.good()) {
518       cerr << starstrings::ssprintf(_(
519 	"Can't open output file `%s' for output; exiting."), argv[3])
520 	   << endl;
521       return EXIT_FAILURE;
522     }
523   }
524 
525   if (strcmp(argv[2], "-") == 0) {
526     if (! cin.good()) {
527       cerr << _("Can't read star data from stdin - not a tty?") << endl;
528       return EXIT_FAILURE;
529     }
530     if (argc == 4) parse_star_file(cin, outfile, specdata, false, add_sun);
531     else           parse_star_file(cin, cout,    specdata, true,  add_sun);
532   }
533 
534   else {
535     infile.open(argv[2]);
536     if (! infile.good()) {
537       cerr << starstrings::ssprintf(_(
538 	"Can't open star data file `%s' for input; exiting."), argv[2])
539 	   << endl;
540       return EXIT_FAILURE;
541     }
542     if (argc == 4) parse_star_file(infile, outfile, specdata, false, add_sun);
543     else           parse_star_file(infile, cout,    specdata, true,  add_sun);
544     infile.close();
545   }
546 
547   if (argc == 4) {
548     outfile.close();
549   }
550 
551   return 0;
552 }
553 
554