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 //
20 // stararray.cc - The Star Array class.  Not much more than a container class
21 //  for stars, with the ability to read and filter a text data file on the fly.
22 //
23 
24 #include "stararray.h"
25 #include "greek.h"
26 
27 #define NEED_FULL_NAMES
28 #include "constellations.h"
29 
30 using std::string;
31 using std::vector;
32 
33 // StarArray private functions -------------------------------------
34 
35 // The usual functions to switch between coordinate systems; as always,
36 //  they don't check to see what coordinate system things are already
37 //  in before blindly applying the transform.  These are private, so the
38 //  check will be done by StarArray::SetRules().
39 
toCelestial()40 void StarArray::toCelestial()
41 { iterate (vector<Star>, Array, i) (*i).toCelestial(); }
42 
toGalactic()43 void StarArray::toGalactic()
44 { iterate (vector<Star>, Array, i) (*i).toGalactic(); }
45 
46 
47 // Function to read an input file (assumes input is a valid opened ifstream)
48 //  and APPEND onto Array all the stars which pass the filters specified
49 //  in ArrayRules.
50 //  Data files are in the following format, with records separated by
51 //  the character RECORD_DELIMITER defined in "stararray.h":
52 //
53 // File header: may not contain the character RECORD_DELIMITER
54 // RECORD_DELIMITER first record (may contain line breaks)
55 // RECORD_DELIMITER second record (likewise)
56 // RECORD_DELIMITER etc.
57 //
58 // [See comment above Star::Star(const char *) in star.cc, or example
59 // StarPlot data file "sample.stars", for detailed structure of records.]
60 
Read(std::ifstream & input)61 void StarArray::Read(std::ifstream &input)
62 {
63   double chartdist = ArrayRules.ChartLocation.magnitude();
64   string garbage;
65   char record[RECORD_MAX_LENGTH + 1];
66   vector<Star> companions = vector<Star>();
67   companions.reserve(100);
68   Star tempstar, faststar;
69 
70   // First, discard all characters before the first record
71   std::getline(input, garbage, RECORD_DELIMITER);
72 
73   // Then loop over records.  std::getline(istream, string, char) would be
74   //  much simpler, but it's _slow_.
75   while (input.get(record, RECORD_MAX_LENGTH + 1, RECORD_DELIMITER)) {
76 
77     // Purge the record delimiter from input and check to see if we've
78     //  exceeded the size of the record string buffer.  If so, ignore
79     //  the rest of that record.
80     if (input.get() != RECORD_DELIMITER)
81       std::getline(input, garbage, RECORD_DELIMITER);
82 
83     // keep track of total # of records in all files we've scanned in,
84     //  and beware of memory overflows
85     TotalStars++;
86 
87     // in the following we want "continue" instead of "return" so that
88     //  TotalStars will be tallied correctly.
89     if (Array.size() >= MAX_STARS) continue;
90 
91     // Filter out stars by distance and magnitude.  This is the fastest
92     //  filter because it doesn't create any temporary objects.  So it
93     //  comes first.
94     {
95       double starmag, stardist;
96       size_t posn = 0, field = 0, len = strlen(record);
97 
98       while (posn < len && field < 3)
99 	if (record[posn++] == FIELD_DELIMITER) field++;
100       stardist = starmath::atof(record + posn);
101       if (stardist > chartdist + ArrayRules.ChartRadius ||
102 	  stardist < chartdist - ArrayRules.ChartRadius) continue;
103 
104       while (posn < len && field < 6)
105 	if (record[posn++] == FIELD_DELIMITER) field++;
106       starmag = starmath::atof(record + posn);
107       if (starmag > ArrayRules.ChartDimmestMagnitude ||
108 	  starmag < ArrayRules.ChartBrightestMagnitude) continue;
109     }
110 
111     // Convert each record which passes the fast filter into a Star and see
112     //  whether it should go into the STL vector of Stars.
113     faststar = Star(record, true /* fast conversion */);
114 
115     if (! ArrayRules.CelestialCoords)  // we assume the data file is in
116       faststar.toGalactic();           // celestial, not galactic, coords
117 
118     if (faststar.PassesFilter(ArrayRules)) {
119       tempstar = Star(record);
120       if (! ArrayRules.CelestialCoords)	tempstar.toGalactic();
121 
122       // If it passes all the filters and is a singleton, primary, or
123       //  sufficiently far from its primary, put it into the STL vector.
124       if (! ArrayRules.ChartHideCompanions
125 	  || (tempstar.GetStarPrimaryDistance() == 0.0)
126 	  || ((tempstar.GetStarPrimaryDistance() * (200 /*pixels*/)
127 	       / ArrayRules.ChartRadius) > MIN_SECONDARY_DISTANCE))
128 	Array.push_back(tempstar);
129 
130       // If it is a companion star, but no membership is given, we have
131       //  no way of telling whether its primary has been filtered out,
132       //  so toss it.
133       else if (tempstar.GetStarMembership()[0].size() == 0) continue;
134 
135       // Put everything else into a separate STL vector to examine later.
136       else companions.push_back(tempstar);
137     }
138   }
139 
140   // Now, go through all the stars in the array of companions.
141   //  Put stars in this array into the main array only if their primaries
142   //  are not already in the main array.
143 
144   iterate (vector<Star>, companions, comp_ptr) {
145     vector<Star>::iterator Array_ptr = Array.begin();
146 
147     if (Array.size() >= MAX_STARS) return;
148     while ((Array_ptr != Array.end())
149 	   && (Array_ptr->GetStarPrimaryDistance() != 0.0
150 	       || ((*comp_ptr).GetStarMembership()[0] !=
151 			  Array_ptr->GetStarMembership()[0])))
152       Array_ptr++;
153     if (Array_ptr == Array.end()) /* no match */ Array.push_back(*comp_ptr);
154   }
155   return;
156 }
157 
158 
159 // Functions to sort stars in order of increasing "local" x-coordinate
160 // XXX: rewrite this with C++ sort() algorithm
161 // First, a typedef to hold all the information the compare function will need
162 
163 typedef struct {
164   double xposition;
165   Star   star;
166 } sortable;
167 
168 // Next, the function to compare for qsort().
169 
compare_function(const void * p,const void * q)170 int compare_function(const void *p, const void *q)
171 {
172   double x1 = ((const sortable *)p)->xposition;
173   double x2 = ((const sortable *)q)->xposition;
174   return (x1 - x2 >= 0.0) ? 1 : -1;
175 }
176 
177 // Finally, the main function which calls qsort()
178 
Sort()179 void StarArray::Sort()
180 {
181   size_t size = Array.size();
182   Vector3 relativeLocation;
183   SolidAngle orientation = ArrayRules.ChartOrientation;
184   sortable *temparray = new sortable[size];
185 
186   // Make a temporary array for qsort(), consisting of "sortable" structs
187   //  which each contain a Star and a position in local coordinates.
188   for (size_t i = 0; i < size; i++) {
189     relativeLocation = Array[i].GetStarXYZ() - ArrayRules.ChartLocation;
190     temparray[i].xposition =
191       relativeLocation.getX() * cos(orientation.getPhi())
192       + relativeLocation.getY() * sin(orientation.getPhi());
193     temparray[i].star = Array[i];
194   }
195 
196   qsort(temparray, size, sizeof(sortable), compare_function);
197 
198   // Put the sorted temporary array back into the vector
199   Array.clear();
200   for (size_t i = 0; i < size; i++) {
201     temparray[i].star.SetPlace(i+1);    // label stars starting at 1
202     Array.push_back(temparray[i].star);
203   }
204 
205   delete [] temparray;
206   return;
207 }
208 
209 
210 // StarArray public functions --------------------------------------
211 
212 
213 // Search(): function to search for a given string in the names of all the
214 //  stars in a set of files.  For safety, this function refuses to work unless
215 //  the array is currently empty.  Stars containing the substring are added
216 //  to Array.  This function uses the "rules" argument to set the correct
217 //  coordinate system (celestial / galactic), but does not bother to set
218 //  its own Rules member.
219 
220 // Options:
221 //
222 //  If `casesensitive' is false, the function ignores the distinction
223 // between upper and lower case.  Otherwise all cases must match.
224 //
225 //  If `exactmatch' is true, `searchstring' must be identical to a star name
226 // (possibly excluding case) for a match after some substitutions have been
227 // performed.  If `exactmatch' is false, the function looks for `searchstring'
228 // as a substring of star names.  If, in addition, `searchstring' contains
229 // spaces, it is tokenized and all of the tokens must be substrings of the
230 // same star name for a match to occur.  No substrings may overlap.
231 //  In either case, some strings and characters are interpreted in special
232 // ways.  See the StarPlot documentation, sections 3.2.1 and 4.3.2, for
233 // details.
234 //
235 //  If `exitonmatch' is true, the function returns only the first match found.
236 // Otherwise all matches are returned.
237 
Search(const std::string & search,StringList filelist,const Rules & rules,bool casesensitive,bool exactmatch,bool exitonmatch)238 void StarArray::Search(const std::string &search, StringList filelist,
239 		       const Rules &rules, bool casesensitive,
240 		       bool exactmatch /* i.e. not a substring */,
241 		       bool exitonmatch /* i.e. return at most one result */)
242 {
243   if (Array.size() || starstrings::isempty(search)) return;
244 
245   string searchstring = search;
246   starstrings::stripspace(searchstring);
247 
248   // Interpret enclosing quote marks
249   unsigned size = searchstring.size();
250   if (size > 2 && searchstring[0] == '"' && searchstring[size - 1] == '"') {
251     searchstring = searchstring.substr(1, size - 2);
252     exactmatch = true;
253   }
254 
255   // Replace real (UTF-8) lowercase Greek letters with their spelled-out names.
256   for (int i = 1; i < NUM_GREEK_LETTERS; i++) {
257     string name;
258     if (! exactmatch) name += '^';
259     name += Greek[i].name;
260 
261     // Make a maximum of one replacement (it would make no sense for
262     // someone to include more than one Greek letter in a search string).
263     if (starstrings::find_and_replace(searchstring, Greek[i].utf8, name))
264       break;
265   }
266 
267   if (! casesensitive) starstrings::toupper(searchstring);
268 
269   // Abbreviate constellation names in the search string, if any.
270   for (int i = 0; i < NUM_CONSTELLATIONS; i++) {
271     string name(constelnames[i]);
272     string abbr;
273     if (!exactmatch) abbr += '[';
274     abbr += constellations[i];
275     if (!exactmatch) abbr += ']';
276     if (! casesensitive) {
277       starstrings::toupper(name);
278       starstrings::toupper(abbr);
279     }
280 
281     // Make a maximum of one replacement (it would make no sense for
282     // someone to include more than one constellation name in a search string).
283     if (starstrings::find_and_replace(searchstring, name, abbr))
284       break;
285   }
286 
287   // Replace asterisks with degree symbols.
288   while (starstrings::find_and_replace(searchstring, "*", DEGREE_UTF8))
289     /* empty body */ ;
290 
291   const StringList temp_tokens(searchstring);
292   StringList start_tokens, end_tokens, tokens;
293 
294   if (! exactmatch) {
295     int last = 0; // index of currently last element of tokens array
296     for (unsigned i = 0; i < temp_tokens.size(); i++, last++) {
297       unsigned s = temp_tokens[i].size();
298       if (s > 1 && temp_tokens[i][0] == '^') {
299 	last--;
300 	start_tokens.push_back(temp_tokens[i].substr(1, s - 1));
301       }
302       else if (s > 2 && temp_tokens[i][0] == '['
303 		     && temp_tokens[i][s - 1] == ']') {
304 	// Replace [ ] with spaces so that a search for "[token]" must turn
305 	// up only instances where "token" appears surrounded by whitespace.
306 	tokens.push_back(temp_tokens[i]);
307 	tokens[last][0] = tokens[last][s - 1] = ' ';
308 
309 	// Also construct an alternate set of strings like " token" to
310 	// be searched for only at the end of each name field.
311 	end_tokens.push_back(tokens[last].substr(0, s - 1));
312       }
313       else {
314 	tokens.push_back(temp_tokens[i]);
315 	end_tokens.push_back(temp_tokens[i]);
316       }
317     }
318 
319     if (start_tokens.size() > 1) // invalid syntax: more than one "^token"
320       return;
321   }
322 
323   // loop over data files
324   iterate (StringList, filelist, filename_ptr) {
325     std::ifstream input;
326     string garbage;
327     char record[RECORD_MAX_LENGTH + 1];
328     int nameposn = 0;
329 
330     input.open((*filename_ptr).c_str());
331     if (!input.good()) continue;
332 
333     // First, discard all characters before the first record
334     std::getline(input, garbage, RECORD_DELIMITER);
335 
336     // Then loop over records.  std::getline(istream, string, char) would be
337     //  much simpler, but it's _slow_.
338     while (input.get(record, RECORD_MAX_LENGTH + 1, RECORD_DELIMITER)) {
339       bool result = false;
340 
341       // Purge the record delimiter from input and check to see if we've
342       //  exceeded the size of the record string buffer.  If so, ignore
343       //  the rest of that record.
344       if (input.get() != RECORD_DELIMITER)
345 	std::getline(input, garbage, RECORD_DELIMITER);
346 
347       StringList namelist(StringList(record, FIELD_DELIMITER)[0],
348 		          SUBFIELD_DELIMITER);
349       namelist.stripspace();
350       if (! casesensitive) namelist.toupper();
351 
352       iterate (StringList, namelist, name_ptr) {
353 	if (exactmatch)
354 	  result = (searchstring == *name_ptr);
355 	else {
356 	  // Search on substrings; all substrings must match within the
357 	  // same star name.  Replace matching substrings with *'s as we go
358 	  // so that found substrings don't overlap (e.g. if someone searches
359 	  // for "Tau Tauri").  We don't delete them completely since that
360 	  // could screw up word matching.
361 	  result = true;
362 	  string temp = *name_ptr;
363 	  if (start_tokens.size()) { // start_tokens.size() == 0 or 1
364 	    unsigned s = start_tokens[0].size();
365 	    if (start_tokens[0] == temp.substr(0, s))
366 	      starstrings::find_and_replace(temp, start_tokens[0], "*");
367 	    else
368 	      result = false;
369 	  }
370 	  if (result)
371 	  for (unsigned i = 0; i < tokens.size(); i++) {
372 	    bool found = false;
373 
374 	    // If this is a word we can safely replace it with a space;
375 	    // otherwise it must be replaced with a '*'
376 	    string replacement = ((tokens[i][0] == ' ') ? " " : "*");
377 	    if (starstrings::find_and_replace(temp, end_tokens[i],
378 				    replacement,
379 				    temp.size() - end_tokens[i].size()))
380 	      found = true;
381 	    else if (starstrings::find_and_replace(temp, tokens[i],
382 				    replacement))
383 	      found = true;
384 
385 	    result = (result && found);
386 	    if (!result) break;
387 	  }
388 	}
389 
390 	if (result) { nameposn = name_ptr - namelist.begin(); break; }
391 
392       } // closes "iterate (StringList, namelist, name_ptr)"
393 
394       if (result) {
395 	Star tempstar = Star(record);
396 	// use Star.sPlace to hold the position of the name in which a match
397 	//  was found:
398 	tempstar.SetPlace(nameposn);
399 	if (! rules.CelestialCoords) tempstar.toGalactic();
400 	Array.push_back(tempstar);
401 
402 	if (exitonmatch || Array.size() >= MAX_STARS) {
403 	  input.close();
404 	  return;
405 	}
406       }
407 
408     } // closes "while (input.get(record, ...))"
409 
410     input.close();
411   } // closes "iterate (StringList, filelist, filename_ptr)"
412 
413   return;
414 }
415 
416 
417 // function to set the rules for the ArrayRules member of StarArray.
418 //  Will also update the Array itself by re-reading the files specified
419 //  in the rules (if necessary) and/or re-sorting the array (if necessary).
420 //  This does not, however, call StarArray::Display(); that's up to the
421 //  main program.
422 
SetRules(const Rules & rules,star_changetype_t ruleschange)423 bool StarArray::SetRules(const Rules &rules, star_changetype_t ruleschange)
424 {
425   ArrayRules = rules;
426 
427  beginning: // if ruleschange is not a valid Changetype, set it to be
428             //  type FILE_CHANGE and jump back here from default: label below
429 
430   switch (ruleschange) {
431     case FILE_CHANGE:
432     case LOCATION_CHANGE: case RADIUS_CHANGE:
433     case COORDINATE_CHANGE: case FILTER_CHANGE:
434       // reload and refilter the file(s) in ArrayRules.ChartFileNames
435       //  (return with an error if none of the file(s) can be read)
436       {
437 	size_t numValidFiles = 0;
438 
439 	TotalStars = 0;
440 	Array.clear();
441 	// will probably not usually display more stars than this at once:
442 	Array.reserve(100);
443 
444 	citerate (StringList, ArrayRules.ChartFileNames, i) {
445 	  std::ifstream data;
446 	  data.open((*i).c_str());
447 	  if (data.good()) {
448 	    Read(data);
449 	    data.close();
450 	    numValidFiles++;
451 	  }
452 	}
453 
454 	// Ideally, we would have a check here to make sure there are not
455 	//  duplicate entries if more than one file was opened.
456 
457 	if (numValidFiles == 0)
458 	  return false;
459       }
460       // fall through...
461 
462     case ORIENTATION_CHANGE: // re-sort Array
463       Sort();
464       // fall through...
465 
466     case DECORATIONS_CHANGE: // search for brightest 8 stars, if necessary
467       if (ArrayRules.StarLabels == LANDMARK_LABEL) {
468 
469 	if (Array.size() <= DISPLAY_N_STARS)
470 	  for (size_t i = 0; i <DISPLAY_N_STARS && i < Array.size(); i++)
471 	    Array[i].SetLabel(true);
472 
473 	else {
474 	  Star *brightarray[DISPLAY_N_STARS];
475 	  double dimmag = -10000;
476 
477 	  // The following variable is set "volatile" to work around a gcc
478 	  //  loop optimization bug which sometimes occurs when compiling
479 	  //  at -O2 or higher.  See one of these URLs for details:
480 	  //  http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=83363
481 	  //  http://www.winehq.com/hypermail/wine-patches/2000/11/0076.html
482 	  //
483 	  //  Supposedly it's fixed in the latest CVS versions.  However,
484 	  //  I still observe it in the Debian-packaged version of the
485 	  //  CVS of g++-3.0.2 of 2001-09-22.
486 	  //
487 	  //  Instead of declaring this "volatile", you can instead compile
488 	  //  this file with the g++ flag -fno-strength-reduce if you want.
489 #if defined __GNUC__ // && (__GNUC__ < 3)
490 	  volatile
491 #endif
492 	  unsigned int dimptr = 0;
493 
494 	  // find the brightest DISPLAY_N_STARS stars and put pointers to them
495 	  //  in brightarray
496 
497 	  for (size_t i = 0; i < DISPLAY_N_STARS; i++) {
498 	    Array[i].SetLabel(false);
499 	    brightarray[i] = &(Array[i]);
500 	    if (brightarray[i]->GetStarMagnitude() > dimmag) {
501 	      dimmag = brightarray[i]->GetStarMagnitude();
502 	      dimptr = i;
503 	    }
504 	  }
505 
506 	  for (size_t i = DISPLAY_N_STARS; i < Array.size(); i++) {
507 	    Array[i].SetLabel(false);
508 
509 	    if (Array[i].GetStarMagnitude() < dimmag) {
510 	      brightarray[dimptr] = &(Array[i]);
511 
512 	      // now find new dimmest star in brightarray
513 	      dimmag = -10000;
514 	      for (size_t j = 0; j < DISPLAY_N_STARS; j++) {
515 		if (brightarray[j]->GetStarMagnitude() > dimmag) {
516 		  dimmag = brightarray[j]->GetStarMagnitude();
517 		  dimptr = j;
518 		}
519 	      }
520 	    }
521 	  }
522 
523 	  // now set those DISPLAY_N_STARS stars to be labeled, as well as
524 	  //  any other stars which have the same magnitude as the dimmest
525 	  //  star in brightarray.
526 	  iterate (StarArray, Array, star_ptr)
527 	    if ((*star_ptr).GetStarMagnitude() <= dimmag)
528 	      (*star_ptr).SetLabel(true);
529 	}
530       }
531     case UNITS_CHANGE: case NO_CHANGE:
532       // do nothing; these changes are taken care of in StarArray::Display()
533       break;
534 
535     default:  // assume the most radical type of change, to be safe
536       ruleschange = FILE_CHANGE;
537       goto beginning;            // yes, a goto statement.  I'm sorry, ok?
538   }
539   return true;
540 }
541 
542 
543 // Drawing the StarArray display -----------------------------------
544 
545 // private functions to draw the chart grid
546 
drawgridnorth(StarViewer * sv,unsigned int wincenterX,unsigned int wincenterY,unsigned int pixelradius) const547 void StarArray::drawgridnorth(StarViewer *sv, unsigned int wincenterX,
548 			      unsigned int wincenterY,
549 			      unsigned int pixelradius) const
550 {
551   sv->setfill(false);
552   sv->setcolor(POSITIVE);
553   sv->drawarc(wincenterX, wincenterY, pixelradius, pixelradius, 0, M_PI);
554   return;
555 }
556 
drawgridsouth(StarViewer * sv,unsigned int wincenterX,unsigned int wincenterY,unsigned int pixelradius) const557 void StarArray::drawgridsouth(StarViewer *sv, unsigned int wincenterX,
558 			      unsigned int wincenterY,
559 			      unsigned int pixelradius) const
560 {
561   sv->setfill(false);
562   sv->setcolor(NEGATIVE);
563   sv->drawarc(wincenterX, wincenterY, pixelradius, pixelradius,
564 	      M_PI, 2 * M_PI);
565   return;
566 }
567 
drawgridequator(StarViewer * sv,unsigned int wincenterX,unsigned int wincenterY,unsigned int pixelradius) const568 void StarArray::drawgridequator(StarViewer *sv, unsigned int wincenterX,
569 				unsigned int wincenterY,
570 				unsigned int pixelradius) const
571 {
572   double p_sinRA = sin(ArrayRules.ChartOrientation.getPhi()) * pixelradius;
573   double p_cosRA = cos(ArrayRules.ChartOrientation.getPhi()) * pixelradius;
574   double sinDec = sin(ArrayRules.ChartOrientation.getTheta());
575   double p_sinRA_sinDec = p_sinRA * sinDec;
576   double p_cosRA_sinDec = p_cosRA * sinDec;
577 
578   // equator
579   sv->setfill(false);
580   if (sinDec >= 0.0) sv->setcolor(POSITIVE);
581   else sv->setcolor(NEGATIVE);
582   sv->drawellipse(wincenterX, wincenterY,
583 		  pixelradius, ROUND(pixelradius * sinDec));
584 
585   // 0 - 12 hr line
586   sv->drawline(wincenterX - ROUND(p_sinRA),
587 	       wincenterY + ROUND(p_cosRA_sinDec),
588 	       wincenterX + ROUND(p_sinRA),
589 	       wincenterY - ROUND(p_cosRA_sinDec));
590 
591   // 6 - 18 hr line
592   sv->drawline(wincenterX + ROUND(p_cosRA),
593 	       wincenterY + ROUND(p_sinRA_sinDec),
594 	       wincenterX - ROUND(p_cosRA),
595 	       wincenterY - ROUND(p_sinRA_sinDec));
596 
597   // RA / GalLong labels
598   vector<string> gridlabels;
599   sv->setcolor(POSITIVE);
600   if (ArrayRules.CelestialCoords)
601     for (unsigned int i = 0; i < 4; i++)
602       gridlabels.push_back(starstrings::itoa(6 * i) +
603 	  /* TRANSLATORS: This is the abbreviation for
604 	     "hours of right ascension". */
605 	  _("h"));
606   else
607     for (unsigned int i = 0; i < 4; i++)
608       gridlabels.push_back(starstrings::itoa(90 * i) + DEGREE_UTF8);
609 
610   sv->drawtext(gridlabels[0].c_str(),
611 	       wincenterX - ROUND(1.1 * p_sinRA) - 5,
612 	       wincenterY + ROUND(1.1 * p_cosRA_sinDec) + 5);
613   sv->drawtext(gridlabels[1].c_str(),
614 	       wincenterX + ROUND(1.1 * p_cosRA) - 5,
615 	       wincenterY + ROUND(1.1 * p_sinRA_sinDec) + 5);
616   sv->drawtext(gridlabels[2].c_str(),
617 	       wincenterX + ROUND(1.1 * p_sinRA) - 5,
618 	       wincenterY - ROUND(1.1 * p_cosRA_sinDec) + 5);
619   sv->drawtext(gridlabels[3].c_str(),
620 	       wincenterX - ROUND(1.1 * p_cosRA) - 5,
621 	       wincenterY - ROUND(1.1 * p_sinRA_sinDec) + 5);
622 
623   return;
624 }
625 
626 
627 // private function to draw a single chart legend item
drawlegenditem(StarViewer * sv,color_t color,unsigned int number,unsigned int x,unsigned int y,unsigned int r,const string & text,int relx,int rely) const628 void StarArray::drawlegenditem(StarViewer *sv, color_t color,
629 			       unsigned int number, unsigned int x,
630 			       unsigned int y, unsigned int r,
631 			       const string &text, int relx, int rely) const
632 {
633   sv->setcolor(color);
634   if (ArrayRules.StarClasses[number])
635     sv->drawstar(x, y, r);
636   else {
637     sv->setfill(false);
638     sv->drawcircle(x, y, r);
639   }
640 
641   sv->setcolor(LABEL_COLOR);
642   sv->drawtext(text, x + relx, y + rely);
643 }
644 
645 
646 // private function to draw the legend
drawlegend(StarViewer * sv) const647 void StarArray::drawlegend(StarViewer *sv) const
648 {
649   unsigned int xbase, ybase = 0;
650   string sra, sdec, sdist, srad, sdmag, sbmag, temp;
651 
652   // first draw the chart information in the upper left
653   if (ArrayRules.CelestialCoords) {
654     sra =
655       /* TRANSLATORS: This is the abbreviation for "right ascension." */
656       string(_("R.A.")) + " ";
657     sdec =
658       /* TRANSLATORS: This is the abbreviation for "declination." */
659       string(_("Dec.")) + " ";
660   }
661   else {
662     sra =
663       /* TRANSLATORS: This is the abbreviation for "longitude". */
664       string(_("Long.")) + " ";
665     sdec =
666       /* TRANSLATORS: This is the abbreviation for "latitude". */
667       string(_("Lat.")) + " ";
668   }
669   SolidAngle gridposn = ArrayRules.ChartLocation.toSpherical();
670   sra += starstrings::ra_to_str(gridposn.getPhi(), ' ',
671 				ArrayRules.CelestialCoords, true);
672   sdec += starstrings::dec_to_str(gridposn.getTheta(), ' ', true);
673   sdist = starstrings::distance_to_str(ArrayRules.ChartLocation.magnitude(),
674 				       ArrayRules.ChartUnits)
675 	  + " " + _("from Earth");
676   srad = string(_("Chart Radius")) + ": " +
677 	  starstrings::distance_to_str(ArrayRules.ChartRadius,
678 				       ArrayRules.ChartUnits);
679   sdmag =
680 	  /* TRANSLATORS: "Mag" is the abbreviation for "magnitude",
681 	     the astronomical brightness of an object. */
682 	  string(_("Dim Mag")) + ": " + starstrings::ftoa(
683 	      starmath::roundoff(ArrayRules.ChartDimmestMagnitude, 2), 4);
684   sbmag =
685 	  /* TRANSLATORS: "Mag" is the abbreviation for "magnitude",
686 	     the astronomical brightness of an object. */
687 	  string(_("Bright Mag")) + ": " + starstrings::ftoa(
688 	      starmath::roundoff(ArrayRules.ChartBrightestMagnitude, 2), 4);
689 
690   // Calculate dimmest apparent magnitude possible for any star permitted
691   // to appear on the chart, and display it.  Useful information in case
692   // the user knows s/he is using a star catalog with a hard lower limit
693   // based on apparent magnitude.
694   double max_distance = ArrayRules.ChartLocation.magnitude()
695 			+ ArrayRules.ChartRadius;
696   float dimmest_apparent_mag = starmath::roundoff(
697     starmath::get_appmag(ArrayRules.ChartDimmestMagnitude, max_distance), 2);
698   sdmag += " (";
699   sdmag += starstrings::ftoa(dimmest_apparent_mag, 4);
700   sdmag += ")";
701 
702   sv->setcolor(BOLD_COLOR); sv->drawtext(_("CHART STATUS"), 15, ybase += 15);
703   sv->setcolor(LABEL_COLOR);
704   sv->drawtext(_("Location of Chart Origin:"), 5, ybase += 15);
705   if (ArrayRules.ChartLocation.magnitude() > 0.0) {
706     sv->drawtext(sra, 10, ybase += 15);
707     sv->drawtext(sdec, 10, ybase += 15);
708   }
709   sv->drawtext(sdist, 10, ybase += 15);
710 
711   if (ArrayRules.ChartRadius <= 0.0) sv->setcolor(ERROR_COLOR);
712   sv->drawtext(srad, 5, ybase += 15);
713 
714   if (ArrayRules.ChartDimmestMagnitude - ArrayRules.ChartBrightestMagnitude
715       <= 0.0)
716     sv->setcolor(ERROR_COLOR);
717   else sv->setcolor(LABEL_COLOR);
718   sv->drawtext(sdmag, 5, ybase += 15);
719   // Show bright mag only if it's been changed from the default
720   if (ArrayRules.ChartBrightestMagnitude > -25)
721     sv->drawtext(sbmag, 5, ybase += 15);
722 
723   // then draw the legend (which doubles as a display of which spectral
724   //  classes are currently turned on) in the upper right
725 
726   xbase = (sv->width() > 100) ? sv->width() - 95 : 5;
727   sv->setcolor(BOLD_COLOR); sv->drawtext(_("LEGEND"), xbase + 5, 15);
728 
729 #define LO LEGEND_OFFSETS
730 
731   drawlegenditem(sv, WOLF_RAYET_COLOR, 0,
732 		 xbase + LO[10][0], LO[10][1], 5,
733 	    /* TRANSLATORS: "Wolf-Rayet" is a proper name of a type of star. */
734 		 _("Wolf-Rayet"));
735   for (unsigned int i = 0; i < 7; i++)
736     drawlegenditem(sv, CLASS_COLORS[i], i, xbase + LO[i][0], LO[i][1], 5,
737 		   string() + SpecClass::int_to_class(i));
738   drawlegenditem(sv, D_COLOR, 7, xbase + LO[7][0], LO[7][1], 3, "wd");
739   drawlegenditem(sv, DEFAULT_COLOR, 8, xbase + LO[8][0], LO[8][1], 5,
740 		 _("Unknown"), -85, 5);
741   drawlegenditem(sv, NON_STELLAR_COLOR, 9, xbase + LO[9][0], LO[9][1], 5,
742 		 _("Non-stellar"), -85, 5);
743   return;
744 }
745 
746 
747 // public function to display all the stars in Array onto the pixmap,
748 //  as well as a grid outline and some other decorations
749 
Display(StarViewer * sv) const750 void StarArray::Display(StarViewer *sv) const
751 {
752   int windowsize, pixelradius, wincenterX, wincenterY;
753   double ChartDec = ArrayRules.ChartOrientation.getTheta();
754   double starZ;
755 
756   // Determine radius and center of chart, in pixels
757   windowsize = (sv->width() > sv->height()) ? sv->height() : sv->width();
758   pixelradius = ROUND(0.4 * windowsize);
759   wincenterX = sv->width() / 2;
760   wincenterY = sv->height() / 2;
761 
762   // clear pixmap with the background color (defined in "specclass.h")
763   sv->fill(BACKGROUND);
764 
765   // draw the hemisphere (N/S) of the grid facing AWAY from the screen
766   if (ArrayRules.ChartGrid) {
767     if (ChartDec >= 0.0)
768       drawgridsouth(sv, wincenterX, wincenterY, pixelradius);
769     else
770       drawgridnorth(sv, wincenterX, wincenterY, pixelradius);
771   }
772 
773   // Draw all the stars which are in the hemisphere of the chart facing
774   //  away from the screen (e.g. the southern hemisphere for
775   //  ArrayRules.ChartOrientation.getTheta() > 0)
776   citerate (StarArray, Array, star_ptr) {
777     starZ = (*star_ptr).GetStarXYZ().getZ() - ArrayRules.ChartLocation.getZ();
778     if (starZ * ChartDec < 0.0)
779       (*star_ptr).Display(ArrayRules, sv);
780   }
781 
782   // Draw the chart equatorial plane and crosshairs
783   if (ArrayRules.ChartGrid)
784     drawgridequator(sv, wincenterX, wincenterY, pixelradius);
785 
786   // Draw all the stars which are in the hemisphere of the chart facing
787   //  toward the screen (e.g. the northern hemisphere for
788   //  ArrayRules.ChartOrientation.getTheta() > 0)
789   citerate (StarArray, Array, star_ptr) {
790     starZ = (*star_ptr).GetStarXYZ().getZ() - ArrayRules.ChartLocation.getZ();
791     if (starZ * ChartDec >= 0.0)
792       (*star_ptr).Display(ArrayRules, sv);
793   }
794 
795   // draw the hemisphere (N/S) of the grid facing TOWARD the screen
796   if (ArrayRules.ChartGrid) {
797     if (ChartDec < 0.0)
798       drawgridsouth(sv, wincenterX, wincenterY, pixelradius);
799     else
800       drawgridnorth(sv, wincenterX, wincenterY, pixelradius);
801   }
802 
803   // put in a legend, if desired
804   if (ArrayRules.ChartLegend) drawlegend(sv);
805 
806   return;
807 }
808 
809 
810 // public function to plot the stars in the StarArray onto a
811 //  Hertzsprung-Russell diagram
812 
Diagram(StarViewer * sv,double brightmag,double dimmag) const813 void StarArray::Diagram(StarViewer *sv, double brightmag, double dimmag) const
814 {
815   int width, height;
816   double magscale = dimmag - brightmag;
817   int scaling = (magscale < 0) ? -1 : 1;
818 
819   // if dimmag < brightmag, this will flip the vertical axis
820   if (magscale < 0)
821     { double tmp = brightmag; brightmag = dimmag; dimmag = tmp; }
822 
823   // Determine size of HR diagram, in pixels, leaving a margin for axis labels
824   width = sv->width() - 80;
825   height = sv->height() - 60;
826 
827   // clear pixmap with the background color (defined in "star.h")
828   sv->fill(BACKGROUND);
829 
830   // draw lines indicating the bright and dim magnitude cutoffs
831   if (ArrayRules.ChartBrightestMagnitude > brightmag
832       && ArrayRules.ChartBrightestMagnitude < dimmag) {
833     int y = (magscale != 0.0) ? ROUND((ArrayRules.ChartBrightestMagnitude -
834 				       brightmag) * height / magscale) : 0;
835     if (magscale < 0) y += height;
836     sv->setcolor(NEGATIVE);
837     sv->drawline(60, y, width + 80, y);
838     sv->drawline(60, y + 5 * scaling, 64, y);
839     sv->drawline(60, y + 4 * scaling, 63, y);
840   }
841   if (ArrayRules.ChartDimmestMagnitude < dimmag
842       && ArrayRules.ChartDimmestMagnitude > brightmag) {
843     int y = (magscale != 0.0) ? ROUND((ArrayRules.ChartDimmestMagnitude -
844 				       brightmag) * height / magscale) : 0;
845     if (magscale < 0) y += height;
846     sv->setcolor(POSITIVE);
847     sv->drawline(60, y, width + 80, y);
848     sv->drawline(60, y - 5 * scaling, 64, y);
849     sv->drawline(60, y - 4 * scaling, 63, y);
850   }
851 
852   // draw the chart axes
853   sv->setcolor(LABEL_COLOR);
854   sv->drawline(60, 0, 60, height);
855   sv->drawline(60, height, width + 80, height);
856 
857   // Plot all the stars
858   citerate (StarArray, Array, star_ptr) {
859     int xposn, yposn;
860     double mag = (*star_ptr).GetStarMagnitude();
861     double numclass = SpecHash((*star_ptr).GetStarClass().classmajor());
862     double numsubclass = (*star_ptr).GetStarClass().classminor();
863 
864     // don't plot stars which would be off-scale:
865     if (mag > dimmag || mag < brightmag) continue;
866 
867     // don't plot non-stellar objects, white dwarfs, or objects without a
868     //  known spectrum subclass (e.g. the '8' in "M8 V"):
869     if (numclass >= 7.0 || numsubclass >= 10.0) continue;
870 
871     numclass += 0.1 * numsubclass;
872     xposn = ROUND(numclass / 7.0 * width);
873     yposn = (magscale != 0.0) ? ROUND((mag - brightmag) * height / magscale):0;
874     if (magscale < 0) yposn += height;
875 
876     // leave a margin for axis labels, hence the "+ 60"
877     (*star_ptr).Draw(ArrayRules, sv, xposn + 60, yposn);
878   }
879 
880   // Draw horizontal axis labels.  Start at x position 56 instead of 60 to
881   //  account for width of letters, so they look better centered.
882   for (unsigned int i = 0; i < 7; i++) {
883     sv->setcolor(ArrayRules.StarClasses[i] ? LABEL_COLOR : DIM_COLOR);
884     sv->drawtext(SpecClass::int_to_class(i),
885 		 56 + width * (2 * i + 1) / 14, height + 20);
886   }
887 
888   // Draw horizontal axis tickmarks.
889   sv->setcolor(LABEL_COLOR);
890   for (unsigned int i = 1; i <= 7; i++)
891     sv->drawline(60 + i * width / 7, height - 2,
892 		 60 + i * width / 7, height + 2);
893 
894   // Draw vertical axis labels and tickmarks.
895   for (int i = FLOOR(brightmag) + 1; i < dimmag; i++) {
896     int y = (magscale != 0.0) ? ROUND((i - brightmag) * height / magscale) : 0;
897     if (magscale < 0) y += height;
898     if (i % 5)
899       sv->drawline(58, y, 62, y);
900     else
901       sv->drawline(56, y, 64, y);
902     if (magscale * scaling < 10 || ! (i % 5)) {
903       string magtext = starstrings::itoa(i);
904       sv->drawtext(magtext, 30, y + 4);
905     }
906   }
907 
908   // Draw axis titles.
909   sv->setcolor(BOLD_COLOR);
910   sv->drawtext(_("SPECTRAL CLASS"), 60 + width / 2 - 50, height + 50);
911 
912   if (magscale == 0.0) sv->setcolor(ERROR_COLOR);
913   sv->drawtext_vertical(_(
914     /* TRANSLATORS: "MAGNITUDE" refers to the astronomical brightness
915        of an object. */
916     "MAGNITUDE"), 10, height / 2 - 60);
917 
918   //for (unsigned int posn = 0; posn < strlen(maglabel); posn++) {
919   //  int y = height / 2 - 60 + 16 * posn;
920   //  sv->drawtext(maglabel[posn], 10, y);
921   //}
922 
923   return;
924 }
925