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