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