1% $Id: pp3.w,v 1.42 2004/08/14 13:46:22 bronger Exp $ 2 3@q======================================================================@> 4@q pp3.w - Star Catalog Chart Creator @> 5@q Copyright 2002--2004 Torsten Bronger @> 6@q <bronger@@users.sourceforge.net> @> 7@q @> 8@q This program may be distributed and/or modified under the @> 9@q conditions of the MIT licence with the following contraint: @> 10@q If you copy or distribute a modified version of this Software, the @> 11@q entire resulting derived work must be given a different name and @> 12@q distributed under the terms of a permission notice identical to @> 13@q this one. @> 14@q @> 15@q For the full licence see below under "*LICENCE*" or the printed @> 16@q version of this program. @> 17@q @> 18@q To get a compilable C++ source code, use @> 19@q ctangle pp3.w - pp3.cc @> 20@q To get a structured documentation of the program, use @> 21@q cweave pp3.w @> 22@q tex pp3 @> 23@q @> 24@q cweave and ctangle belong to the cweb system by Levy/Knuth. @> 25@q Further information can be obtained at @> 26@q http://www-cs-faculty.stanford.edu/~knuth/cweb.html @> 27@q======================================================================@> 28 29\catcode`@@=11 30 31% First the default font setup 32 33\font\ninett=cmtt9 34\font\nineit=cmti9 35 36\font\stitlefont=cmss10 scaled\magstep3 % sans serif type in title 37\font\sbtitlefont=cmssbx10 scaled\magstep3 % sans bold type in title 38\font\sf=cmss10 \font\sfbf=cmssbx10 39\font\sfa=cmss7 40 41%% From here with Palatino 42\font\tenrm=pplr7t % roman text 43\font\ninerm=pplr7t scaled 900 44\font\eightrm=pplr7t scaled 800 45\font\sevenrm=pplr7t scaled 700 46\font\fiverm=pplr7t scaled 500 47 48\font\teni=zplmr7m % math italic (zptmcm7m for Times, zplmr7m for Palatino) 49\font\seveni=zplmr7m scaled 700 50\font\fivei=zplmr7m scaled 500 51 52\font\tensy=zplmr7y % math symbols (zptmcm7y for Times, zplmr7y for Palatino) 53\font\sevensy=zplmr7y scaled 700 54\font\fivesy=zplmr7y scaled 500 55 56\font\tenex=zplmr7v % math extension (zptmcm7v for Times, zplmr7v f. Palatino) 57 58\font\tenbf=pplb7t % boldface extended 59\font\sevenbf=pplb7t scaled 700 60\font\fivebf=pplb7t scaled 500 61 62\font\tentt=cmtt10 % typewriter 63\font\ninett=cmtt9 % typewriter 64 65\font\tensl=pplro7t % slanted roman 66 67\font\tenit=pplri7t % text italic 68 69\skewchar\teni='177 \skewchar\seveni='177 \skewchar\fivei='177 70\skewchar\tensy='60 \skewchar\sevensy='60 \skewchar\fivesy='60 71 72\textfont0=\tenrm \scriptfont0=\sevenrm \scriptscriptfont0=\fiverm 73\def\rm{\fam\z@@\tenrm} 74\textfont1=\teni \scriptfont1=\seveni \scriptscriptfont1=\fivei 75\def\mit{\fam\@@ne} \def\oldstyle{\fam\@@ne\teni} 76\textfont2=\tensy \scriptfont2=\sevensy \scriptscriptfont2=\fivesy 77\def\cal{\fam\tw@@} 78\textfont3=\tenex \scriptfont3=\tenex \scriptscriptfont3=\tenex 79\newfam\itfam \def\it{\fam\itfam\tenit} % \it is family 4 80\textfont\itfam=\tenit 81\newfam\slfam \def\sl{\fam\slfam\tensl} % \sl is family 5 82\textfont\slfam=\tensl 83\newfam\bffam \def\bf{\fam\bffam\tenbf} % \bf is family 6 84\textfont\bffam=\tenbf \scriptfont\bffam=\sevenbf 85\scriptscriptfont\bffam=\fivebf 86\newfam\ttfam \def\tt{\fam\ttfam\tentt} % \tt is family 7 87\textfont\ttfam=\tentt 88 89\catcode`@@=12 90 91\font\eightpplr=pplr7t scaled 800 92\font\ninepplr=pplr7t scaled 900 93\font\ninepplri=pplri7t scaled 900 94\font\tenpplr=pplr7t 95\font\tenpplb=pplb7t 96\font\tenpplri=pplri7t 97\font\titlefont=pplr7t scaled 1728 % title on the contents page 98\font\ttitlefont=cmtt10 scaled\magstep2 % typewriter type in title 99\font\tentex=cmtex10 % TeX extended character set (used in strings) 100\fontdimen7\tentex=0pt % no double space after sentences 101 102\let\mainfont=\tenpplr 103\let\sc=\ninepplr 104\let\mc=\ninepplr 105\let\it=\tenpplri 106\let\nineit=\ninepplri 107\let\tt=\tentt 108\let\cmntfont\tenpplr 109 110\mainfont\baselineskip12.7pt 111 112%% Now for the title page 113 114\font\sf=bfrr8t \font\sfbf=bfrb8t 115\font\sfa=bfrr8t scaled 700 116 117\font\stitlefont=phvr7t scaled\magstep3 % sans serif type in title 118\font\sbtitlefont=phvb7t scaled\magstep3 % sans bold type in title 119\font\ttitlefont=pcrb7t scaled\magstep3 % typewriter type in title 120 121\font\stitlefont=bfrr8t scaled\magstep3 % sans serif type in title 122\font\sbtitlefont=bfrb8t scaled\magstep3 % sans serif type in title 123%\font\sbtitlefont=0t3x8r scaled\magstep3 % sans bold type in title 124%\font\sf=0t3r8r \font\sfbf=0t3b8r 125%\font\sfa=0t3r8r scaled 700 126 127 128\hyphenation{white-space} 129 130\secpagedepth=1 131\def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX} 132\def\LaTeX{L\kern-.36em% 133 {\setbox0=\hbox{T}% 134 \vbox to\ht0{\hbox{\sevenrm A}\vss}% 135 }% 136 \kern-.15em% 137 \TeX} 138\def\LaTeXbf{L\kern-.36em% 139 {\setbox0=\hbox{T}% 140 \vbox to\ht0{\hbox{\sevenbf A}\vss}% 141 }% 142 \kern-.15em% 143 \TeX} 144\def\AMSinner{\cal A\mkern-2mu\raise-0.5ex\hbox{$\cal M$}\mkern-2muS} 145\def\AMS{\ifmmode\AMSinner\else$\AMSinner$\fi} 146\def\BSC/{{\mc BSC\spacefactor1000}} 147\def\HSB/{{\mc HSB\spacefactor1000}} 148\def\EPS/{{\mc EPS\spacefactor1000}} 149\def\PS/{{\mc PS\spacefactor1000}} 150\def\PDF/{{\mc PDF\spacefactor1000}} 151\def\RGB/{{\mc RGB\spacefactor1000}} 152\def\NGC/{{\mc NGC\spacefactor1000}} 153\def\IC/{{\mc IC\spacefactor1000}} 154\def\HD/{{\mc HD\spacefactor1000}} 155 156\def\9#1{} 157 158\def\sloppy{\tolerance9999\emergencystretch3em\hfuzz .5pt\vfuzz\hfuzz} 159 160\def\title{PP3 (Version 1.3)} 161\def\topofcontents{\null\vfill\vskip-1.5cm 162 \centerline{\titlefont The Sky Map Creator 163 {\sbtitlefont PP\lower.35ex\hbox{3}}} 164 \vskip 15pt 165 \centerline{(Version 1.3)} 166 \vfill} 167\def\botofcontents{\parindent2em\vfill\vfill\vfill\vfill\vfill 168\noindent 169% 170% *LICENCE* 171% 172% 173Copyright \copyright\ 2002--2004 Torsten Bronger 174 ({\tt bronger@@users.sourceforge.net}) 175\ninerm\baselineskip0.9\baselineskip\let\it\nineit 176\bigskip\noindent 177Permission is hereby granted, free of charge, to any person obtaining a copy of 178this software and associated documentation files (the ``Software''), to deal in 179the Software without restriction, including without limitation the rights to 180use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 181of the Software, and to permit persons to whom the Software is furnished to do 182so, subject to the following conditions: 183 184\leftskip1cm\rightskip\leftskip 185\medskip\noindent 186The above copyright notice and this permission notice shall be included in all 187copies or substantial portions of the Software. 188 189If you copy or distribute a modified version of this Software, the entire 190resulting derived work must be given a different name and distributed under the 191terms of a permission notice identical to this one. 192 193\leftskip0cm\rightskip\leftskip\bigskip\noindent 194The Software is provided ``as~is'', without warranty of any kind, express or 195implied, including but not limited to the warranties of {\it merchantability}, 196{\it fitness for a particular purpose\/} and {\it noninfringement}. In no 197event shall the authors or copyright holders be liable for any claim, damages 198or other liability, whether in an action of contract, tort or otherwise, 199arising from, out of or in connection with the Software or the use or other 200dealings in the Software.\vskip-1cm} 201 202\def\PPTHREE/{{\sf PP\lower.35ex\hbox{3}}} 203 204@i c++lib.w 205@s ios int 206 207@** Introduction. \ifpdftex (Please note that you can find a table of contents 208at the end of this document). \fi This program \PPTHREE/ (``parvum 209planetarium'') takes the data of various celestial data files and turns them 210into a \LaTeX\ file that uses \PS/Tricks to draw a nice sky chart containing a 211certain region of the sky. Current versions are available on its 212\pdfURL{homepage}{http://pp3.sourceforge.net}. 213 214You call \PPTHREE/ with e.\,g.\medskip 215 216\.{pp3 mychart.pp3} 217 218\medskip\noindent The data files (\.{stars.dat}, \.{nebulae.dat}, 219\.{boundaries.dat}, \.{labeldimens.dat}, \.{lines.dat}, and 220\.{milkyway}\hskip0pt\.{.dat}) must be in the proper directory. The proper 221directory was compiled into \PPTHREE/. With Linux, normally it's 222\.{/usr/local/share/pp3/}, with Windows the current directory simply. But the 223environment variable \.{PP3DATA} can override that. 224 225The resulting chart is by default sent to standard output which you may 226redirect into a file. But you can define an output filename explicitly in the 227input script. 228 229If you want to use other data with this program, you may well provide your own 230catalogue files. Their file formats are very simple, and they are explained in 231this document together with the respective |read_@t\dots@>()| function. 232 233If you give a single dash ``\.{-}'' as the only parameter to \PPTHREE/, the 234input script is read from standard input. So if you write\medskip 235 236\.{pp3 - > test.tex \AM\AM{} latex test \AM\AM{} dvips test} 237 238\medskip\noindent and type \.{\CF D} (\hbox{Control-D})\footnote{$^1$}{it's 239Control-Z-Return on Windows}, a file \.{test.ps} should be produced that 240contains a sample chart. 241 242Very important is to know how to write an input script. Please consult the 243following section ``The input script'' for this. Here is an example input 244script: \medskip 245 246{\parindent2em\baselineskip10.5pt\ninett\obeylines\obeyspaces 247\# Chart of the Scorpion, printable on a 248\# black and white printing device 249\vskip\baselineskip 250set constellation SCO \# This is highlighted 251set center\UL{}rectascension 17 252set center\UL{}declination -28 253set grad\UL{}per\UL{}cm 2.5 254\vskip\baselineskip 255switch milky\UL{}way on 256switch eps\UL{}output on \# Please call LaTeX and dvips for us 257switch colored\UL{}stars off \# All the same colour ... 258color stars 0 0 0 \# ...\ namely this one 259color nebulae 0 0 0 260color background 1 1 1 261color grid 0.5 0.5 0.5 262color ecliptic 0.3 0.3 0.3 263color constellation\UL{}lines 0.7 0.7 0.7 264color labels 0 0 0 265color boundaries 0.8 0.8 0.8 266color highlighted\UL{}boundaries 0 0 0 267color milky\UL{}way 0.5 0.5 0.5 268\vskip\baselineskip 269filename output test.tex \# Here should it go 270\vskip\baselineskip 271\vskip\baselineskip 272objects\UL{}and\UL{}labels \# Now for the second part 273\vskip\baselineskip 274delete M 18 NGC 6590 NGC 6634 ; \# Delete superfluous 275reposition SCO 20 S ; \# Force sig Sco to be labelled. 276text "\BS\BS{}Huge Sco" at 16.2 -41.5 color 0 0 0 towards NW ; 277} 278 279@ The resulting \LaTeX\ file doesn't use packages special to \PPTHREE/. In 280fact the preamble is rather small. This makes it possible to copy the (maybe 281huge) \.{\BS vbox} with the complete map into an own \LaTeX\ file. However 282this may be a stupid decision because (especially if the Milky Way is switched 283on) this consumes much of \TeX's pool size. 284 285Some \PPTHREE/ figures will need a lot of \TeX's memory. Normally this is 286problematic only if the Milky Way is included. If you show a too large portion 287of the sky, and a lot of Milky Way area is involved, it may even happen that 288\LaTeX\ simply cannot process it. You should use Milky Way data with lower 289resolution in this case. 290 291Make sure that the \PS/Tricks package is properly installed. 292 293Please note that you cannot use pdf\/\LaTeX\ with \PPTHREE/, because \PS/Tricks 294can't be used with pdf\/\LaTeX\spacefactor1000. This is not a problem. First, 295you can convert all \EPS/ files easily to \PDF/ by hand, and secondly \PPTHREE/ 296can even call \PS/2\PDF/ for you. 297 298@ In order to use the program, you must have a complete and modern \LaTeX\ 299distribution installed, and a modern Ghostscript. On a Linux system, both 300programs are possibly already available, and if not you may install them with a 301package management program. 302 303{\sloppy On Windows, you will probably have to install them by hand. You can 304download the \pdfURL{Mik\TeX\ distribution}{http://www.miktex.org} or get the 305\pdfURL{\TeX{}Live~{\mc CD}}{http://www.tug.org/texlive/}\spacefactor1000. If 306you install 307\pdfURL{Ghostscript}{http://www.cs.wisc.edu/\TILDE/ghost/doc/AFPL/get811.htm}, 308notice that \pdfURL{{\mc 309GS}View}{http://www.cs.wisc.edu/\TILDE/ghost/gsview/get45.htm} is a very 310sensible program, too.\par} 311 312@ Some flaws and bugs in this program are already known to its author. 313 314The input script processing is shaky. The comment character `\.{\#}' must be 315at the beginning of a line or must be preceded by whitespace. The special 316token ``\.{objects\UL and\UL labels}'' must not occur within strings. If an 317error is found in the input script, \PPTHREE/ doesn't tell the line number. It 318should be possible to include more than one file, and it should allow for a 319nesting depth greater than one. 320 321At the moment almost all data structures are kept in memory completely. For 322the author's needs this is perfectly sufficient, however if you want to use 323data bases with hundreds of thousands of objects, you will run into trouble. 324On the other hand it's only necessary to keep all object in memory that are 325actually drawn. So memory usage could be reduced drastically. 326 327@ Okay, let's start with the header files~\dots 328 329@q'}}}}@> 330 331@c 332#include <iostream> 333#include <string> 334#include <fstream> 335#include <sstream> 336#include <vector> 337#include <map> 338#include <list> 339#include <iomanip> 340#include <cstdlib> 341#include <cmath> 342#include <cfloat>@# 343 344using namespace std; 345 346@<Missing math routines@>@;@# 347 348@* Global parameters and default values. I have to break a strict \CPLUSPLUS/ 349rule here: Never use \hbox{|#@t\hskip-\fontdimen2\mainfont@>define|}! However 350I really found no alternative to~|OUT|. No |const| construct worked, and if it 351had done, I'd have to use it in every single routine. And ubiquitous 352|*params.out|'s are ugly. 353 354@d OUT (*params.out) 355 356 357@ The following makes it possible to compile a directory prefix into \PPTHREE/ 358for the data files. By default, the data files must be in the current 359directory. You may say e.\,g.\ $$\hbox{\.{g++ 360-DPP3DATA=\BS"/usr/share/pp3/\BS" -O2 -s pp3.cc -o pp3}}$$ then they are 361searched in \.{/usr/share/pp3/}. If set, an environment variable called 362\.{PP3DATA} has highest priority though. 363 364\def\NULL{0} 365 366@s NULL TeX 367 368@c 369const char* pp3data_env_raw = getenv("PP3DATA"); 370const string pp3data_env = (pp3data_env_raw == NULL ? 371 "" : pp3data_env_raw); 372#ifdef PP3DATA 373const string pp3data(PP3DATA); 374#else 375const string pp3data; 376#endif 377const string filename_prefix(!pp3data_env.empty() ? 378 pp3data_env + '/' : (pp3data.empty() ? 379 "" : pp3data + "/")); 380 381 382@ I declare {\it and\/} define the structure |parameters| here. 383 384@c 385@<Define |color| data structure@>@;@# 386 387struct parameters { 388 double center_rectascension, center_declination; 389 double view_frame_width, view_frame_height; 390 double grad_per_cm; 391 double label_skip, minimal_nebula_radius, faintest_cluster_magnitude, 392 faintest_diffuse_nebula_magnitude, faintest_star_magnitude, 393 star_scaling, minimal_star_radius, faintest_star_disk_magnitude, 394 faintest_star_with_label_magnitude, shortest_constellation_line; 395 string constellation; 396 int font_size; 397 double penalties_label, penalties_star, penalties_nebula, 398 penalties_boundary, penalties_boundary_rim, penalties_cline, 399 penalties_cline_rim, penalties_threshold, penalties_rim; 400 string filename_stars, filename_nebulae, filename_dimensions, 401 filename_lines, filename_boundaries, filename_milkyway, 402 filename_preamble, filename_include; 403 string filename_output; 404 ostream* out; 405 istream* in; 406 bool input_file; 407 color bgcolor, gridcolor, eclipticcolor, boundarycolor, hlboundarycolor, 408 starcolor, nebulacolor, labelcolor, textlabelcolor, clinecolor, 409 milkywaycolor; 410 double linewidth_grid, linewidth_ecliptic, linewidth_boundary, 411 linewidth_hlboundary, linewidth_cline, linewidth_nebula; 412 string linestyle_grid, linestyle_ecliptic, linestyle_boundary, 413 linestyle_hlboundary, linestyle_cline, linestyle_nebula; 414 bool milkyway_visible, nebulae_visible, colored_stars, show_grid, 415 show_ecliptic, show_boundaries, show_lines, show_labels; 416 bool create_eps, create_pdf; 417 parameters() : @<Default values of all global parameters@> { } 418 int view_frame_width_in_bp() { 419 return int(ceil(view_frame_width / 2.54 * 72)); 420 } 421 int view_frame_height_in_bp() { 422 return int(ceil(view_frame_height / 2.54 * 72)); 423 } 424} params; 425 426@ @<Default values of all global parameters@>= 427center_rectascension(5.8), center_declination(0.0), 428 view_frame_width(15.0), view_frame_height(15.0), 429 grad_per_cm(4.0), 430 constellation("ORI"), font_size(10), @/ 431 label_skip(0.06), minimal_nebula_radius(0.1), 432 faintest_cluster_magnitude(4.0), 433 faintest_diffuse_nebula_magnitude(8.0), 434 faintest_star_magnitude(7.0), star_scaling(1.0), 435 minimal_star_radius(0.015), 436 faintest_star_disk_magnitude(4.5), 437 faintest_star_with_label_magnitude(3.7), 438 shortest_constellation_line(0.1), @/ 439 penalties_label(1.0), penalties_star(1.0), 440 penalties_nebula(1.0), penalties_boundary(1.0), 441 penalties_boundary_rim(1.0), penalties_cline(1.0), 442 penalties_cline_rim(1.0), penalties_threshold(1.0), 443 penalties_rim(1.0), @/ 444 filename_stars(filename_prefix + "stars.dat"), 445 filename_nebulae(filename_prefix + "nebulae.dat"), 446 filename_dimensions("labeldimens.dat"), 447 filename_lines(filename_prefix + "lines.dat"), 448 filename_boundaries(filename_prefix + "boundaries.dat"), 449 filename_milkyway(filename_prefix + "milkyway.dat"), @/ 450 filename_preamble(), filename_include(), 451 filename_output(), out(&cout), in(0), input_file(false), @/ 452 bgcolor("bgcolor", 0, 0, 0.4), 453 gridcolor("gridcolor", 0, 0.298, 0.447), 454 eclipticcolor("eclipticcolor", 1, 0, 0), 455 boundarycolor("boundarycolor", 0.5, 0.5, 0), 456 hlboundarycolor("hlboundarycolor", 1, 1, 0), 457 starcolor("starcolor", 1, 1, 1), 458 nebulacolor("nebulacolor", 1, 1, 1), 459 labelcolor("labelcolor", 0, 1, 1), 460 textlabelcolor(1, 1, 0), 461 clinecolor("clinecolor", 0, 1, 0), 462 milkywaycolor(0, 0, 1), @/ 463 linewidth_grid(0.025), linewidth_ecliptic(0.018), 464 linewidth_boundary(0.035), linewidth_hlboundary(0.035), 465 linewidth_cline(0.035), linewidth_nebula(0.018), @/ 466 linestyle_grid("solid"), linestyle_ecliptic("dashed"), 467 linestyle_boundary("dashed"), 468 linestyle_hlboundary("dashed"), linestyle_cline("solid"), 469 linestyle_nebula("solid"), @/ 470 milkyway_visible(false), 471 nebulae_visible(true), 472 colored_stars(true), 473 show_grid(true), show_ecliptic(true), show_boundaries(true), 474 show_lines(true), show_labels(true), @/ 475 create_eps(false), 476 create_pdf(false) 477 478 479@** The input script. The input script is a text file that is given as the 480first and only parameter to \PPTHREE/. Its format is very simple: First, a 481`\.{\#}' introduces a comment and the rest of the line is ignored. Secondly, 482every command has an opcode--parameter(s) form. Thirdly, opcodes and 483parameters are separated by whitespace (no matter which type and how much). 484Forthly, parameters and celestial objects must be separated by 485``\.{objects\_and\_labels}''. 486 487@* Part~I: Global parameters. Every input script can be divided into two 488parts, however the second may be absent. They are separated from each other by 489the token ``\.{objects\_and\_labels}''. Here we process the first part of the 490input script. 491 492First two small helping routines that just read simple values from the file. 493 494@q'@> 495 496@<Routines for reading the input script@>= 497bool read_boolean(istream& script) { 498 string boolean; 499 script >> boolean; 500 if (boolean == "on") return true; 501 else if (boolean == "off") return false; 502 else throw string("Expected \"on\" or \"off\" in \"switch\" construct " 503 "in input script"); 504} 505 506@ You can give strings in a similar way as on a shell command line: If it 507doesn't contain spaces, just input it. In the other case you have to enclose 508it within \.{"..."}. The same is necessary if it starts with a~\.{"}. Within 509the double braces, backslashes and double braces have to be escaped with a 510backslash. This is not necessary if you had a simple string. 511 512So you may write e.\,g.: \.{Leo}, \.{"Leo Minor"}, \.{\BS alpha}, and 513\.{"\LB\BS\BS sfseries Leo\RB"}. An empty string can only be written as 514\.{""}. 515 516@q'@> 517 518@<Routines for reading the input script@>= 519string read_string(istream& script) { 520 const string UnexpectedEOS("Unexpected end of input script while reading a" 521 " string parameter"); 522 char c; 523 string contents; 524 while (script.get(c)) if (!isspace(c)) break; 525 if (!script) throw UnexpectedEOS; 526 if (c != '"') { 527 script >> contents; 528 if (script) contents.insert(contents.begin(),c); else contents = c; 529 } else { 530 while (script.get(c)) { 531 if (c == '"') break; 532 if (c == '\\') { 533 if (!script.get(c)) 534 throw UnexpectedEOS; 535 switch (c) { 536 case '\\': case '"': contents += c; break; 537 default: throw string("Unknown escape sequence in string"); 538 } 539 } else contents += c; 540 } 541 if (!script) 542 throw UnexpectedEOS; 543 } 544 return contents; 545} 546 547@ {\sloppy Here the actual routine for this first part. The top-level keywords 548are: ``\.{color}'', ``\.{line\_width}'', ``\.{line\_style}'', ``\.{switch}'', 549``\.{penalties}'', ``\.{filename}'', and ``\.{set}''.\par} 550 551@.color@> 552@.line\_width@> 553@.line\_style@> 554@.switch@> 555@.penalties@> 556@.filename@> 557@.set@> 558 559@<Routines for reading the input script@>= 560void read_parameters_from_script(istream& script) { 561 string opcode; 562 script >> opcode; 563 while (opcode != "objects_and_labels" && script) { 564 if (opcode[0] == '#') { // skip comment line 565 string rest_of_line; 566 getline(script,rest_of_line); 567 } else 568 @<Set color parameters@>@; 569 else 570 @<Set line widths@>@; 571 else 572 @<Set line styles@>@; 573 else 574 @<Set on/off parameters@>@; 575 else 576 @<Set penalty parameters@>@; 577 else 578 @<Set filename parameters@>@; 579 else 580 @<Set single value parameters@>@; 581 else throw string("Undefined opcode in input script: \"") 582 + opcode + '"'; 583 script >> opcode; 584 } 585} 586 587@ {\sloppy Colours are given as red--green--blue values from $0$ to~$1$. For example, 588$$\hbox{\.{color labels 1 0 0}}$$ which makes all labels red. The following 589sub-keywords can be used: ``\.{background}'', ``\.{grid}'', 590``\.{eclip}\-\.{tic}'', ``\.{boundaries}'', ``\.{highlighted\_boundaries}'', 591``\.{stars}'', ``\.{nebulae}'', ``\.{labels}'', 592``\.{text\_}\hskip0pt\.{labels}'', ``\.{constellation\_}\hskip0pt\.{lines}'', 593and ``\.{milky\_way}''. In case of the milky way, the colour denotes the 594brightest regions. (The darkest have \.{back}\-\.{ground} colour.)\par} 595 596@.color@> 597@.background@> 598@.grid@> 599@.ecliptic@> 600@.boundaries@> 601@.highlighted\_boundaries@> 602@.stars@> 603@.nebulae@> 604@.labels@> 605@.text\_labels@> 606@.constellation\_lines@> 607@.milky\_way@> 608 609@<Set color parameters@>= 610 if (opcode == "color") { 611 string color_name; 612 script >> color_name; 613 if (color_name == "background") script >> params.bgcolor; 614 else if (color_name == "grid") script >> params.gridcolor; 615 else if (color_name == "ecliptic") script >> params.eclipticcolor; 616 else if (color_name == "boundaries") 617 script >> params.boundarycolor; 618 else if (color_name == "highlighted_boundaries") 619 script >> params.hlboundarycolor; 620 else if (color_name == "stars") script >> params.starcolor; 621 else if (color_name == "nebulae") script >> params.nebulacolor; 622 else if (color_name == "labels") script >> params.labelcolor; 623 else if (color_name == "text_labels") 624 script >> params.textlabelcolor; 625 else if (color_name == "constellation_lines") 626 script >> params.clinecolor; 627 else if (color_name == "milky_way") script >> params.milkywaycolor; 628 else throw string("Undefined \"color\" construct" 629 " in input script: \"") + color_name + '"'; 630 } 631 632@ {\sloppy\raggedright The following lines can be modified: ``\.{grid}'', 633``\.{ecliptic}'', ``\.{boundaries}'', 634``\.{highlighted\_}\hskip0pt\.{boundaries}'', ``\.{nebulae}'', and 635``\.{constellation\_lines}''. The linewidth in centimetres must follow.\par} 636 637@.line\_width@> 638@.grid@> 639@.ecliptic@> 640@.boundaries@> 641@.highlighted\_boundaries@> 642@.nebulae@> 643@.constellation\_lines@> 644 645@<Set line widths@>= 646 if (opcode == "line_width") { 647 string line_name; 648 script >> line_name; 649 if (line_name == "grid") script >> params.linewidth_grid; 650 else if (line_name == "ecliptic") 651 script >> params.linewidth_ecliptic; 652 else if (line_name == "boundaries") 653 script >> params.linewidth_boundary; 654 else if (line_name == "highlighted_boundaries") 655 script >> params.linewidth_hlboundary; 656 else if (line_name == "nebulae") 657 script >> params.linewidth_nebula; 658 else if (line_name == "constellation_lines") 659 script >> params.linewidth_cline; 660 else throw string("Undefined \"line_width\" construct" 661 " in input script: \"") + line_name + '"'; 662 } 663 664@ {\sloppy\raggedright The following lines can be modified: ``\.{grid}'', 665``\.{ecliptic}'', ``\.{boundaries}'', 666``\.{highlighted\_}\hskip0pt\.{boundaries}'', ``\.{nebulae}'', and 667``\.{constellation\_lines}''. You can set the respective line style to 668``\.{solid}'', ``\.{dashed}'', and ``\.{dotted}''.\par} 669 670@.line\_style@> 671@.solid@> 672@.dashed@> 673@.dotted@> 674@.grid@> 675@.ecliptic@> 676@.boundaries@> 677@.highlighted\_boundaries@> 678@.nebulae@> 679@.constellation\_lines@> 680 681@<Set line styles@>= 682 if (opcode == "line_style") { 683 string line_name; 684 script >> line_name; 685 if (line_name == "grid") script >> params.linestyle_grid; 686 else if (line_name == "ecliptic") 687 script >> params.linestyle_ecliptic; 688 else if (line_name == "boundaries") 689 script >> params.linestyle_boundary; 690 else if (line_name == "highlighted_boundaries") 691 script >> params.linestyle_hlboundary; 692 else if (line_name == "nebulae") 693 script >> params.linestyle_nebula; 694 else if (line_name == "constellation_lines") 695 script >> params.linestyle_cline; 696 else throw string("Undefined \"line_width\" construct" 697 " in input script: \"") + line_name + '"'; 698 } 699 700 701@ {\sloppy There are the following boolean values: ``\.{milky\_may}'', 702``\.{nebulae}'', ``\.{colored\_stars}'', ``\.{grid}'', ``\.{ecliptic}'', 703``\.{boundaries}'', ``\.{constellation\_lines}'', ``\.{labels}'', 704``\.{eps\_output}'', and ``\.{pdf\_output}''. You can switch them ``\.{on}'' 705or ``\.{off}''.\par} 706 707@.switch@> 708@.on@> 709@.off@> 710@.milky\_way@> 711@.nebulae@> 712@.colored\_stars@> 713@.grid@> 714@.ecliptic@> 715@.boundaries@> 716@.constellation\_lines@> 717@.labels@> 718@.eps\_output@> 719@.pdf\_output@> 720 721@<Set on/off parameters@>= 722 if (opcode == "switch") { 723 string switch_name; 724 script >> switch_name; 725 if (switch_name == "milky_way") 726 params.milkyway_visible = read_boolean(script); 727 else if (switch_name == "nebulae") 728 params.nebulae_visible = read_boolean(script); 729 else if (switch_name == "colored_stars") 730 params.colored_stars = read_boolean(script); 731 else if (switch_name == "grid") 732 params.show_grid = read_boolean(script); 733 else if (switch_name == "ecliptic") 734 params.show_ecliptic = read_boolean(script); 735 else if (switch_name == "boundaries") 736 params.show_boundaries = read_boolean(script); 737 else if (switch_name == "constellation_lines") 738 params.show_lines = read_boolean(script); 739 else if (switch_name == "labels") 740 params.show_labels = read_boolean(script); 741 else if (switch_name == "eps_output") 742 params.create_eps = read_boolean(script); 743 else if (switch_name == "pdf_output") 744 params.create_pdf = read_boolean(script); 745 else throw string("Undefined \"switch\" construct" 746 " in input script: \"") + switch_name + '"'; 747 } 748 749@ In order to avoid overlaps, \PPTHREE/ uses a simple penalty algorithm. The 750standard value for all penalty values is~1000. The meanings of ``\.{stars}'', 751``\.{labels}'', ``\.{nebulae}'', ``\.{boundaries}'', and 752``\.{constellation\_lines}'' is pretty easy to explain: They come into play 753when the current label (that is to be positioned) overlaps with the respective 754object. For example, if you want overlaps with constellation lines to be less 755probable, you can say $$\hbox{\.{penalties constellation\_lines 2000}}$$ 756 757There is another concept of importance here: The rim. A rim is a rectangular 758margin around every label with a width of |skip|. Overlaps in the rim are 759counted, too, however normally they don't hurt that much. Normally they hurt 760half as much as the label area ({\it core}) itself, but this can be changed 761with ``\.{rim}''. With $$\hbox{\.{penalties rim 0}}$$ the rim loses its 762complete significance. But notice that for each core penalty a rim penalty is 763added, too, so that the rim can never be more significant than the core. 764 765Within the rim, ``\.{boundaries\_rim}'' and ``\.{constellation\_lines\_rim}'' 766are used instead of the normal ones. This is because lines are not so bad in 767the rim as other stars or nebulae would be, because other stars in the vicinity 768of a label may cause confusion, lines not. 769 770\medskip The third thing about penalties is the maximal penalty of a label. If 771the penalties of a label exceed this value, the label is supressed. You may 772overrule this behaviour with an explicit repositioning of the label. You can 773adjust this maximal badness of the label with ``\.{threshold}''. With 774$$\hbox{\.{penalties threshold 10000}}$$ probably all labels are printed. 775 776@.penalties@> 777@.stars@> 778@.labels@> 779@.nebulae@> 780@.boundaries@> 781@.boundaries\_rim@> 782@.constellation\_lines@> 783@.constellation\_lines\_rim@> 784@.threshold@> 785@.rim@> 786 787@q'@> 788 789@<Set penalty parameters@>= 790 if (opcode == "penalties") { 791 string penalty_name; 792 double value; 793 script >> penalty_name >> value; 794 value /= 1000.0; 795 if (penalty_name == "stars") 796 params.penalties_star = value; 797 else if (penalty_name == "labels") 798 params.penalties_label = value; 799 else if (penalty_name == "nebulae") 800 params.penalties_nebula = value; 801 else if (penalty_name == "boundaries") 802 params.penalties_boundary = value; 803 else if (penalty_name == "boundaries_rim") 804 params.penalties_boundary_rim = value; 805 else if (penalty_name == "constellation_lines") 806 params.penalties_cline = value; 807 else if (penalty_name == "constellation_lines_rim") 808 params.penalties_cline_rim = value; 809 else if (penalty_name == "threshold") 810 params.penalties_threshold = value; 811 else if (penalty_name == "rim") 812 params.penalties_rim = value; 813 else throw string("Undefined \"penalties\" construct" 814 " in input script: \"") + penalty_name + '"'; 815 } 816 817@ @q(@> The most important filename is ``\.{output}''. By default it's unset 818so that the output is sent to standard output. With $$\hbox{\.{filename output 819orion.tex}}$$ the output is written to \.{orion.tex}. Most of the other 820filenames denote the data files. Their file format is described at the 821functions that read them. Their names are: ``\.{stars}'', ``\.{nebulae}'', 822``\.{label\_dimensions}'', ``\.{constellation\_lines}'', ``\.{boundaries}'', 823and ``\.{milky\_way}''. 824 825``\.{latex\_preamble}'' is a file with a \LaTeX\ excerpt with a preamble 826fragment for the \LaTeX\ output. See |@<|create_preamble()| for writing the 827\LaTeX\ preamble@>|. 828 829``\.{include}'' denotes a file that is included at the current position as an 830insert script file. This command particularly makes sense at the very 831beginning of the input script because then you can overwrite the values 832locally. Note that you can only include zero or one file, and included script 833files must not contain further \.{include}s. Apart from that included scripts 834have the same structure as usual scripts. (This is also true for a possible 835`\.{objects\_and\_labels}' part.) 836 837@q;@> 838 839The meaning of this is of course to write a master script with global settings 840(e.\,g.\ colour, line style, data file names etc.), and to overwrite them for 841certain regions of the sky, typically stellar constellations. 842 843@.output@> 844@.stars@> 845@.nebulae@> 846@.label\_dimensions@> 847@.constellation\_lines@> 848@.boundaries@> 849@.milky\_way@> 850@.latex\_preamble@> 851@.include@> 852 853@<Set filename parameters@>= 854 if (opcode == "filename") { 855 string object_name; 856 script >> object_name; 857 if (object_name == "output") 858 params.filename_output = read_string(script); 859 else if (object_name == "stars") 860 params.filename_stars = read_string(script); 861 else if (object_name == "nebulae") 862 params.filename_nebulae = read_string(script); 863 else if (object_name == "label_dimensions") 864 params.filename_dimensions = read_string(script); 865 else if (object_name == "constellation_lines") 866 params.filename_lines = read_string(script); 867 else if (object_name == "boundaries") 868 params.filename_boundaries = read_string(script); 869 else if (object_name == "milky_way") 870 params.filename_milkyway = read_string(script); 871 else if (object_name == "latex_preamble") 872 params.filename_preamble = read_string(script); 873 else if (object_name == "include") { 874 if (!params.filename_include.empty()) 875 throw string("Nesting depth of include files greater" 876 " than one"); 877 params.filename_include = read_string(script); 878 ifstream included_script(params.filename_include.c_str()); 879 if (!included_script.good()) 880 cerr << "pp3: Warning: included file " 881 << params.filename_include 882 << " not found; ignored" << endl; 883 else read_parameters_from_script(included_script); 884 } 885 else throw string("Undefined \"filename\" construct" 886 " in input script: \"") + object_name + '"'; 887 } 888 889 890@ Most of these values are numeric, only \.{constellation} is a string, namely 891a three-letter all-uppercase astronomic abbreviation of the constellation to be 892highlighted. It's default is ``\.{ORI}'' but you may set it to the empty 893string with $$\hbox{\.{set constellation ""}}$$ so no constellation gets 894highlighted. At the moment highlighting means that the boundaries have a 895brighter colour than normal. 896 897{\sloppy\raggedright 898``\.{center\_rectascension}'' and ``\.{center\_declination}'' are the celestial 899coordinates of the view frame centre. ``\.{box\_width}'' and 900``\.{box\_height}'' are the dimensions of the view frame in centimetres. 901``\.{grad\_per\_cm}'' is the magnification (scale). 902``\.{star\_scaling}'' denotes a radial scaling of the star 903disks. ``\.{fontsize}'' is the global \LaTeX\ font size (in points). It must 904be 10, 11, or~12. Default is~10. 905 906Many parameters deal with the graphical representation of stars and nebulae: 907``\.{shortest\_constellation\_line}'' is the shortest length for a 908constellation line that is supposed to be drawn. ``\.{label\_skip}'' is the 909distance between label and celestial object. ``\.{minimal\_nebula\_radius}'' 910is the radius under which a nebula is drawn as a mere circle of {\it that\/} 911radius. ``\.{minimal\_star\_radius}'' is the radius of the smallest stellar 912dots of the graphics. All these parameters are measured in centimetres. 913 914But there are also some magnitudes: The faintest stellar clusters that are 915drawn by default are of the ``\.{faintest\_cluster\_magnitude}'', all other 916nebulae drawn by default of the ``\.{faintest\_diffuse\_nebula\_magnitude}''. 917Stars brighter than ``\.{faintest\_star\_magnitude}'' are drawn at all, if they 918are even brighter than ``\.{faintest\_star\_with\_label\_magnitude}'' they get 919a label. Stars brighter than ``\.{faintest\_star\_disk\_magnitude}'' are not 920just mere dots in the background, but get a radius according to their 921brightness.\par} 922 923Many of these parameters trigger the default behaviour that you can overrule by 924commands in the second part of the input script. 925 926@.set@> 927@.center\_rectascension@> 928@.center\_declination@> 929@.box\_width@> 930@.box\_height@> 931@.grad\_per\_cm@> 932@.shortest\_constellation\_line@> 933@.label\_skip@> 934@.minimal\_nebula\_radius@> 935@.faintest\_cluster\_magnitude@> 936@.faintest\_diffuse\_nebula\_magnitude@> 937@.faintest\_star\_magnitude@> 938@.minimal\_star\_radius@> 939@.faintest\_star\_disk\_magnitude@> 940@.faintest\_star\_with\_label\_magnitude@> 941@.star\_scaling@> 942@.constellation@> 943@.fontsize@> 944 945@q)'@> 946 947@<Set single value parameters@>= 948 if (opcode == "set") { 949 string param_name; 950 script >> param_name; 951 if (param_name == "center_rectascension") 952 script >> params.center_rectascension; 953 else if (param_name == "center_declination") 954 script >> params.center_declination; 955 else if (param_name == "box_width") 956 script >> params.view_frame_width; 957 else if (param_name == "box_height") 958 script >> params.view_frame_height; 959 else if (param_name == "grad_per_cm") 960 script >> params.grad_per_cm; 961 else if (param_name == "constellation") 962 params.constellation = read_string(script); 963 else if (param_name == "shortest_constellation_line") 964 script >> params.shortest_constellation_line; 965 else if (param_name == "label_skip") 966 script >> params.label_skip; 967 else if (param_name == "minimal_nebula_radius") 968 script >> params.minimal_nebula_radius; 969 else if (param_name == "faintest_cluster_magnitude") 970 script >> params.faintest_cluster_magnitude; 971 else if (param_name == "faintest_diffuse_nebula_magnitude") 972 script >> params.faintest_diffuse_nebula_magnitude; 973 else if (param_name == "faintest_star_magnitude") 974 script >> params.faintest_star_magnitude; 975 else if (param_name == "minimal_star_radius") 976 script >> params.minimal_star_radius; 977 else if (param_name == "faintest_star_disk_magnitude") 978 script >> params.faintest_star_disk_magnitude; 979 else if (param_name == "faintest_star_with_label_magnitude") 980 script >> params.faintest_star_with_label_magnitude; 981 else if (param_name == "star_scaling") 982 script >> params.star_scaling; 983 else if (param_name == "fontsize") 984 script >> params.font_size; 985 else 986 throw string("Undefined \"set\" construct in input script: \"") 987 + param_name + '"'; 988 } 989 990@* Part~II: Change printed objects and labels. Here I read and interpret the 991second part of the input script, {\it after\/} the |"objects_and_labels"|. 992This part doesn't need to be available, and both parts may be empty. 993 994First I define a type that is often used in the following routines for a 995mapping from a catalogue number on the index in \PPTHREE/'s internal |vectors|. 996This makes access a lot faster. 997 998@<Routines for reading the input script@>= 999typedef map<int,int> index_list; 1000 1001@ Here I create the data structures that make the above mentioned mapping 1002possible. FixMe: They should be defined globally, so that the constellation 1003lines construction can profit by it, too. And then they needn't be given in 1004the parameter lists of the routines. 1005 1006This mapping is not vital for the program, but the alternative would be to look 1007through the whole of |nebulae| or |stars| to find a star with a certain \NGC/ 1008or \HD/ number. This is probably way to inefficient. 1009 1010@q'@> 1011 1012@<Create mapping structures for direct catalogue access@>= 1013 const int max_ngc = 7840, max_ic = 5386, max_messier = 110; 1014 index_list ngc, ic, messier; 1015 for (int i = 0; i < nebulae.size(); i++) { 1016 if (nebulae[i].ngc > 0 && nebulae[i].ngc <= max_ngc) 1017 ngc[nebulae[i].ngc] = i; 1018 if (nebulae[i].ic > 0 && nebulae[i].ic <= max_ic) 1019 ic[nebulae[i].ic] = i; 1020 if (nebulae[i].messier > 0 && nebulae[i].messier <= max_messier) 1021 messier[nebulae[i].messier] = i; 1022 } 1023 index_list henry_draper; 1024 map<string, index_list> flamsteed; 1025 for (int i = 0; i < stars.size(); i++) { 1026 if (stars[i].hd > 0) henry_draper[stars[i].hd] = i; 1027 if (stars[i].flamsteed > 0) 1028 flamsteed[stars[i].constellation][stars[i].flamsteed] = i; 1029 } 1030 1031 1032@ This is a general warning producing routine. Such tests are vital, 1033especially if the nebulae file has been deleted in order to save disk space. I 1034want it to be a warning because it would be very annowing to delete all such 1035opcodes from an input script only because one wants to dispense with e.\,g.\ 1036nebulae. 1037 1038I need three incarnations of this function: One for nebulae, one for Flamsteed 1039number, and one for Henry Draper catalogue numbers. 1040 1041@<Routines for reading the input script@>= 1042bool assure_valid_catalogue_index(const int index, const index_list& catalogue, 1043 const string catalogue_name) { 1044 if (catalogue.find(index) == catalogue.end()) { 1045 cerr << "pp3: Warning: Invalid " << catalogue_name << " index: " 1046 << index << ".\n"; 1047 return false; 1048 } else return true; 1049} 1050 1051bool assure_valid_catalogue_index(const int index, 1052 const string constellation, 1053 map<string,index_list>& flamsteed) { 1054 bool found = true; 1055 if (flamsteed.find(constellation) == flamsteed.end()) found = false; 1056 else if (flamsteed[constellation].find(index) == 1057 flamsteed[constellation].end()) found = false; 1058 if (!found) cerr << "pp3: Warning: Invalid Flamsteed number: " 1059 << index << ".\n"; 1060 return found; 1061} 1062 1063bool assure_valid_catalogue_index(const int index, 1064 const index_list& henry_draper) { 1065 if (henry_draper.find(index) == henry_draper.end()) { 1066 cerr << "pp3: Warning: Invalid HD number: " 1067 << index << ".\n"; 1068 return false; 1069 } else return true; 1070} 1071 1072@ In this routine I scan a list of stellar objects, given by a token pair of 1073catalogue name and catalogue index. Such lists are used after some top-level 1074commands below. A mandatory `\.{;}' ends such a list. Five catalogues are 1075supported: \NGC/, \IC/, Messier, Henry-Draper~(\HD/), and Flamsteed numbers (in 1076the form ``{\it Constellation}\SP{\it Flamsteed number}''). You may use the 1077program `Celestia' to get the \HD/ numbers for the stars. 1078 1079@<Routines for reading the input script@>= 1080void search_objects(istream& script, 1081 index_list& ngc, index_list& ic, 1082 index_list& messier, index_list& henry_draper, 1083 map<string, index_list>& flamsteed, 1084 vector<int>& found_stars, vector<int>& found_nebulae) { 1085 found_stars.clear(); 1086 found_nebulae.clear(); 1087 string catalogue_name; 1088 int catalogue_index; 1089 script >> catalogue_name; 1090 while (script && catalogue_name != ";") { 1091 script >> catalogue_index; 1092 if (catalogue_index <= 0) { 1093 stringstream error_message; 1094 error_message << "Invalid index: " << catalogue_index; 1095 throw error_message.str(); 1096 } 1097 if (!script) throw string("Unexpected end of input script"); 1098 1099 if (params.nebulae_visible || 1100 (catalogue_name != "NGC" && catalogue_name != "IC" && 1101 catalogue_name != "M")) { 1102 if (catalogue_name == "NGC") { 1103 if (assure_valid_catalogue_index(catalogue_index,ngc,"NGC")) 1104 found_nebulae.push_back(ngc[catalogue_index]); 1105 } else if (catalogue_name == "IC") { 1106 if (assure_valid_catalogue_index(catalogue_index,ic,"IC")) 1107 found_nebulae.push_back(ic[catalogue_index]); 1108 } else if (catalogue_name == "M") { 1109 if (assure_valid_catalogue_index(catalogue_index,messier,"M")) 1110 found_nebulae.push_back(messier[catalogue_index]); 1111 } else if (catalogue_name == "HD") { 1112 if (assure_valid_catalogue_index(catalogue_index,henry_draper)) 1113 found_stars.push_back(henry_draper[catalogue_index]); 1114 } else { 1115 if (assure_valid_catalogue_index(catalogue_index, 1116 catalogue_name,flamsteed)) 1117 found_stars. 1118 push_back(flamsteed[catalogue_name][catalogue_index]); 1119 } 1120 } 1121 script >> catalogue_name; 1122 } 1123} 1124 1125@ This routine essentially does the same as the prevous one, however only for 1126{\it one\/} celestial object. This is used for commands that don't take an 1127object list but only one object. 1128 1129@q'@> 1130 1131@<Routines for reading the input script@>= 1132view_data* identify_object(istream& script, index_list& ngc, 1133 index_list& ic, index_list& messier, 1134 index_list& henry_draper, 1135 map<string, index_list>& flamsteed, 1136 stars_list& stars, nebulae_list& nebulae) { 1137 string catalogue_name; 1138 int catalogue_index; 1139 script >> catalogue_name >> catalogue_index; 1140 if (!script) throw string("Unexpected end of input script"); 1141 if (catalogue_name == "NGC") { 1142 if (assure_valid_catalogue_index(catalogue_index,ngc,"NGC")) 1143 return &nebulae[ngc[catalogue_index]]; 1144 } else if (catalogue_name == "IC") { 1145 if (assure_valid_catalogue_index(catalogue_index,ic,"IC")) 1146 return &nebulae[ic[catalogue_index]]; 1147 } else if (catalogue_name == "M") { 1148 if (assure_valid_catalogue_index(catalogue_index,messier,"M")) 1149 return &nebulae[messier[catalogue_index]]; 1150 } else if (catalogue_name == "HD") { 1151 if (assure_valid_catalogue_index(catalogue_index,henry_draper)) 1152 return &stars[henry_draper[catalogue_index]]; 1153 } else { 1154 if (assure_valid_catalogue_index(catalogue_index, 1155 catalogue_name,flamsteed)) 1156 return &stars[flamsteed[catalogue_name][catalogue_index]]; 1157 } 1158 return 0; 1159} 1160 1161@ Here now the main routine for the second part of the input script. The 1162top-level commands in this section are: ``\.{reposition}'', 1163``\.{delete\_labels}'', ``\.{add\_labels}'', ``\.{delete}'', ``\.{add}'', 1164``\.{set\_text\_label}'', and ``\.{text}''. 1165 1166@.reposition@> 1167@.delete\_labels@> 1168@.add\_labels@> 1169@.delete@> 1170@.add@> 1171@.text@> 1172@.set\_label\_text@> 1173 1174@<Routines for reading the input script@>= 1175void read_objects_and_labels(istream& script, 1176 const dimensions_list& dimensions, 1177 objects_list& objects, stars_list& stars, 1178 nebulae_list& nebulae, 1179 texts_list& texts, 1180 flexes_list& flexes, 1181 const transformation& mytransform, 1182 bool included = false) { 1183 if (!params.filename_include.empty() && !included) { 1184 ifstream included_file(params.filename_include.c_str()); 1185 @<Skip everything till |"objects_and_labels"|@>@; 1186 read_objects_and_labels(included_file, dimensions, objects, stars, 1187 nebulae, texts, flexes, mytransform, true); 1188 } 1189 string opcode; 1190 script >> opcode; 1191 if (!script) return; 1192 @<Create mapping structures for direct catalogue access@>@; 1193 while (script) { 1194 if (opcode[0] == '#') { // skip comment line 1195 string rest_of_line; 1196 getline(script,rest_of_line); 1197 } else 1198 @<Label repositioning@>@; 1199 else 1200 @<Change label text@>@; 1201 else 1202 @<Text labels@>@; 1203 else { // command with objects list 1204 vector<int> found_stars, found_nebulae; 1205 @<Label deletion@>@; 1206 else 1207 @<Label activation@>@; 1208 else 1209 @<Celestial object deletion@>@; 1210 else 1211 @<Celestial object activation@>@; 1212 else throw string("Undefined opcode in input script: \"") 1213 + opcode + '"'; 1214 } 1215 script >> opcode; 1216 } 1217} 1218 1219@ The following algorithm is so bad that it must be considered a bug. It 1220simply searches for the string |"objects_and_labels"| in the |included_file|, 1221but that may occur within a string or wherever. The file should be properly 1222scanned, but I'm too lazy for that. A case where this bug becomes visible 1223should be very rare anyway. 1224 1225FixMe: Fix this bug, or at least for strings. Maybe the format of input 1226scripts must be changed significantly for this. 1227 1228@q'@> 1229 1230@<Skip everything till |"objects_and_labels"|@>= 1231 string token; 1232 included_file >> token; 1233 while (included_file) { 1234 if (token[0] == '#') getline(included_file,token); 1235 else if (token == "objects_and_labels") break; 1236 included_file >> token; 1237 } 1238 1239 1240@ Sometimes labels have an unfortunate position. But you may say e.\,g.\ 1241$$\hbox{\.{reposition M 42 E ;}}$$ to position the label for the Orion Nebula 1242to the right of it. (Abbreviations are taken from the wind rose.) You may use 1243this command to force a certain label to be drawn although \PPTHREE/ has 1244decided that there is no space for it and didn't print it in the first place. 1245 1246If you write ``\.{E\UL}'' or ``\.{W\UL}'' the label text is not vertically 1247centred but aligned with the celestrial coordinates at the {\it baseline\/} of 1248the label text. 1249 1250At the moment, this command takes exactly one argument. However the closing 1251semicolon is necessary. 1252 1253@q'@> 1254 1255@<Label repositioning@>= 1256 if (opcode == "reposition") { 1257 string position, semicolon; 1258 view_data* current_object = 1259 identify_object(script, ngc, ic, messier, henry_draper, 1260 flamsteed, stars, nebulae); 1261 int angle; 1262 bool on_baseline = false; 1263 script >> position >> semicolon; 1264 if (semicolon != ";") 1265 throw string("Expected \";\" after \"reposition\" command"); 1266 @<Map a wind rose |position| to an |angle| 1267 and determine |on_baseline|@>@; 1268 if (current_object) { 1269 current_object->label_angle = angle; 1270 current_object->with_label = visible; 1271 current_object->on_baseline = on_baseline; 1272 current_object->label_arranged = true; 1273 } 1274 } 1275 1276@ @<Map a wind rose |position| to an |angle| and determine |on_baseline|@>= 1277 if (position == "E" || position == "E_") angle = 0; 1278 else if (position =="NE") angle = 1; 1279 else if (position =="N") angle = 2; 1280 else if (position =="NW") angle = 3; 1281 else if (position =="W" || position == "W_") angle = 4; 1282 else if (position =="SW") angle = 5; 1283 else if (position =="S") angle = 6; 1284 else if (position =="SE") angle = 7; 1285 else throw string("Undefined position angle: ") + position; 1286 on_baseline = position[position.length()-1] == '_'; 1287 1288 1289@ \PPTHREE/ reads default labels from the stars and nebulae data files. But 1290you may say e.\,g.\ $$\hbox{\.{set\_label\_text ORI 19 Rigel}}$$ to rename 1291``$\alpha$'' (Orionis) to ``Rigel''. 1292 1293@<Change label text@>= 1294 if (opcode == "set_label_text") { 1295 view_data* current_object = 1296 identify_object(script, ngc, ic, messier, henry_draper, 1297 flamsteed, stars, nebulae); 1298 if (current_object) current_object->label = read_string(script); 1299 } 1300 1301 1302@ With e.\,g.\ $$\hbox{\.{delete\_labels M 35 M 42 ;}}$$ you delete the labels 1303(not the nebulae themselves!)\ of M\,35 and M\,42. 1304 1305@<Label deletion@>= 1306 if (opcode == "delete_labels") { 1307 search_objects(script, ngc, ic, messier, henry_draper, 1308 flamsteed, found_stars, found_nebulae); 1309 for (int i = 0; i < found_stars.size(); i++) { 1310 stars[found_stars[i]].with_label = hidden; 1311 stars[found_stars[i]].label_arranged = true; 1312 } 1313 for (int i = 0; i < found_nebulae.size(); i++) { 1314 nebulae[found_nebulae[i]].with_label = hidden; 1315 nebulae[found_nebulae[i]].label_arranged = false; 1316 } 1317 } 1318 1319@ The counterpart of \.{delete\_labels}. It makes sense first and foremost for 1320stars. (Unfortunately this means that you have to use extensively the Henry 1321Draper Catalogue.) 1322 1323@<Label activation@>= 1324 if (opcode == "add_labels") { 1325 search_objects(script, ngc, ic, messier, henry_draper, 1326 flamsteed, found_stars, found_nebulae); 1327 for (int i = 0; i < found_stars.size(); i++) 1328 stars[found_stars[i]].with_label = visible; 1329 for (int i = 0; i < found_nebulae.size(); i++) 1330 nebulae[found_nebulae[i]].with_label = visible; 1331 } 1332 1333@ This adds objects (mostly nebulae) the the field. Notice that this object is 1334then printed even if it lies outside the view frame (it may be clipped though). 1335 1336@<Celestial object activation@>= 1337 if (opcode == "add") { 1338 search_objects(script, ngc, ic, messier, henry_draper, 1339 flamsteed, found_stars, found_nebulae); 1340 for (int i = 0; i < found_stars.size(); i++) 1341 stars[found_stars[i]].in_view = visible; 1342 for (int i = 0; i < found_nebulae.size(); i++) 1343 nebulae[found_nebulae[i]].in_view = visible; 1344 } 1345 1346@ The opposite of |@<Celestial object activation@>|. 1347 1348@<Celestial object deletion@>= 1349 if (opcode == "delete") { 1350 search_objects(script, ngc, ic, messier, henry_draper, 1351 flamsteed, found_stars, found_nebulae); 1352 for (int i = 0; i < found_stars.size(); i++) 1353 stars[found_stars[i]].in_view = hidden; 1354 for (int i = 0; i < found_nebulae.size(); i++) 1355 nebulae[found_nebulae[i]].in_view = hidden; 1356 } 1357 1358@ This is the only way to add a text label. The parameters are the text 1359itself, rectascension, declination, \RGB/ colour, and the relative position 1360(uppercase wind rose), followed by a semicolon. For example, 1361$$\hbox{\.{text Leo at 11 10 color 1 0 0 towards S ;}}$$ puts a red ``Leo'' 1362centered below the point $(11\,\hbox{h}, +10^\circ)$ in the Lion. You may 1363leave out the ``\.{color}'' and/or the ``\.{towards}'' option. The default 1364colour is the last given colour in a previous \.{text} command, or if this 1365doesn't exist the current text label colour. The default value of 1366``\.{towards}'' is~|"NE"|. 1367 1368The contents of a text label is eventually in an \.{\BS hbox}, so you can use 1369that fact. You can also use all \PS/Tricks commands. For example, with 1370\medskip 1371 1372{\parindent2em\tentt 1373text "\BS\BS psdots[dotstyle=+,dotangle=45](0,0)\BS\BS scriptsize\BS\BS{} N 1374 pole of ecliptic"\par 1375\ \ \ \ \ at 18 66.56 towards E\UL{} ; 1376} 1377 1378\medskip\noindent the $\times$ marks the northern ecliptical pole, together 1379with a small label text. If such things occur frequently, define it as a 1380\LaTeX\ macro. 1381 1382If you write ``\.{E\UL}'' or ``\.{W\UL}'' the label text is not vertically 1383centred but aligned with the celestrial coordinates at the {\it baseline\/} of 1384the label text. 1385 1386\medskip A ``declination flex'' is a special text label that stands on a circle 1387of equal declination which can look very nice. You get it with the option 1388``\.{along declination}'' in the parameter list of the text label. 1389 1390\medskip If you use the \.{tics} option the label is printed along a 1391rectascension or declination circle multiple times, making it possible to print 1392tic marks. For example, \medskip 1393 1394{\parindent2em\tentt 1395text "\$\#3\$" at 0 20 along declination tics rectascension 1 towards N ;\par 1396text "\$\#5\$" at 11 0 along declination tics declination 10 towards S ; 1397} 1398 1399\medskip\noindent creates automatically generated labels for the $20^\circ$ 1400declination circle (every whole hour), and for the $11^{\sevenrm h}$ 1401rectascension line (every 10 declination degrees). See the explanation for 1402|@<Generate |contents| from current coordinates@>| for further details. 1403 1404@.text@> 1405@.along@> 1406@.declination@> 1407@.tics@> 1408 1409@<Text labels@>= 1410if (opcode == "text") { 1411 string token, contents, position("NE"); 1412 double rectascension, declination; 1413 contents = read_string(script); 1414 script >> token; 1415 if (token != "at") throw string("\"at\" in \"text\" command expected"); 1416 script >> rectascension >> declination; 1417 script >> token; 1418 int angle = 1; // |"NE"| 1419 bool on_baseline = false; 1420 enum { kind_text_label, kind_flex_declination} label_kind 1421 = kind_text_label; 1422 double step_rectascension = -1.0; // $-1$ means that no maks are produced. 1423 double step_declination = -1.0; 1424 while (script && token != ";") { 1425 if (token == "color") script >> params.textlabelcolor; 1426 else if (token == "towards") { 1427 script >> position; 1428 @<Map a wind rose |position| to an |angle| 1429 and determine |on_baseline|@>@; 1430 } 1431 else if (token == "along") { 1432 script >> token; 1433 if (token == "declination") { 1434 label_kind = kind_flex_declination; 1435 } else throw string("Invalid \"along\" option"); 1436 } 1437 else if (token == "tics") { 1438 script >> token; 1439 if (token == "rectascension") { 1440 script >> step_rectascension; 1441 if (step_rectascension <= 0.0) 1442 throw string("Invalid \"tics\" interval"); 1443 step_declination = -1.0; 1444 } else if (token == "declination") { 1445 script >> step_declination; 1446 if (step_declination <= 0.0) 1447 throw string("Invalid \"tics\" interval"); 1448 step_rectascension = -1.0; 1449 } else throw string("Invalid \"tics\" option"); 1450 } else throw string("Invalid \"text\" option"); 1451 script >> token; 1452 } 1453 if (!script) 1454 throw string("Unexpected end of script while scanning \"text\""); 1455 if (step_rectascension <= 0.0 && step_declination <= 0.0) { 1456 @<Insert text label into label container structure@>@; 1457 } if (step_rectascension > 0.0) { 1458 string user_pattern(contents); 1459 double start = rectascension - floor(rectascension/step_rectascension) 1460 * step_rectascension; 1461 for (rectascension = start; rectascension < 24.0; 1462 rectascension += step_rectascension) { cerr << rectascension << ' '; 1463 @<Generate |contents| from current coordinates@>@; 1464 @<Insert text label into label container structure@>@; 1465 } 1466 } else if (step_declination > 0.0) { 1467 string user_pattern(contents); 1468 double start = declination - floor((declination+90.0) / step_declination) 1469 * step_declination; 1470 for (declination = start; declination <= 90.0; 1471 declination += step_declination) { cerr << declination << ' '; 1472 @<Generate |contents| from current coordinates@>@; 1473 @<Insert text label into label container structure@>@; 1474 } 1475 } 1476} 1477 1478@ FixMe: Eventually this section must be a clean function call. I insert a 1479text label or a flex into the correct data structure. 1480 1481@<Insert text label into label container structure@>= 1482 switch (label_kind) { 1483 case kind_text_label: 1484 texts.push_back(text(contents, rectascension, declination, 1485 params.textlabelcolor, angle, on_baseline)); 1486 break; 1487 case kind_flex_declination: 1488 flexes.push_back(new declination_flex(contents, rectascension, 1489 declination, 1490 params.textlabelcolor, angle, 1491 on_baseline)); 1492 break; 1493 } 1494 1495@ FixMe: Eventually this section must be a clean function call. Here I create 1496the \LaTeX\ macro for the tics marks. It is called \.{\BS coordinates} and has 1497nine parameters, however not all are used so far:\medskip 1498 1499\item{\tt\#1} Rectascension in hours. 1500\item{\tt\#2} Declination in degrees. 1501\item{\tt\#3} Rectascension in integer hours (truncated, not rounded). 1502\item{\tt\#4} Rectascension fraction of hour in minutes (truncated, not 1503 rounded). 1504\item{\tt\#5} Declination in rounded integer degrees. 1505 1506\medskip All decimal points are replaced by ``\.{\{\\DP\}}'' commands. 1507 1508@<Generate |contents| from current coordinates@>= 1509 { 1510 stringstream coordinates; 1511 coordinates.setf(ios::fixed); 1512 coordinates << "\\def\\coordinates#1#2#3#4#5#6#7#8#9{\\TicMark{" 1513 << user_pattern << "}}"; 1514 coordinates << "\\coordinates{" << rectascension << "}{" 1515 << declination << "}{"; 1516 coordinates << int(floor(rectascension)) << "}{" 1517 << int(floor((rectascension - floor(rectascension)) 1518 * 60.0)) 1519 << "}{"; 1520 if (int(declination) > 0.0) coordinates << '+'; 1521 coordinates << int(declination) << "}{}{}{}{}"; 1522 contents = coordinates.str(); 1523 while (contents.find(".") != string::npos) 1524 contents.replace(contents.find("."),1,"{\\DP}"); 1525 } 1526 1527@** Data structures and file formats. First I describe the data structures that 1528directly contain celestial objects such as stars and nebulae. This is a little 1529bit unfortunate for the program itself, because actually other things should be 1530defined first (e.\,g.\ |dimension|). However I think that the program can be 1531digested more easily this way. 1532 1533All data structures are |struct|s and no |class|es. The reason is that they 1534are all very simple and all object oriented security measures are only 1535hindering. 1536 1537For each data type called {\bf classname} I define a container called {\bf 1538classnames\_list} that is a |vector|. For almost each data type I define a 1539routine that can read it from a file in the vector. There I also decribe the 1540respective file format. 1541 1542@* View data: Positioning and labels. This first |struct| can be used as an 1543ancestor in the various celestial objects data structurs (e.\,g.\ stars) for 1544containing all view frame dependent information. Most importantly it contains 1545the label of a celestial object, its position relatively to the object, and its 1546size. 1547 1548|in_view| is |visible| if the object is actually displayed on screen. |x| and 1549 |y| contain the screen coordinates in centimetres. |radius| is the radial 1550 size of the object in centimetres. |skip| is given in centimetres, too. It 1551 denotes the space between the outer boundary of the object (enclosed by 1552 |radius|) and the label. 1553 1554|with_label| is |visible| if the object has a label, with |label_width|, 1555 |label_height|, |label_depth| (estimated in centimetres) and |label_angle|. 1556 {\it Please note the all heights in \PPTHREE/ are {\rm total} heights, thus 1557 the same as \TeX's height${}+{}$depth!} 1558 1559|on_baseline| is true, if the {\it baseline\/} of the label text is supposed to 1560 be vertically aligned with the celestial position. (This works not for all 1561 |label_angle|s.) 1562 1563|label_arranged| is |true| if the best place for the label has been found 1564 already. Only then the label should be really printed, but the real use of 1565 |label_arranged| is that it avoids the label to be arranged 1566 twice. |lable_angle| can only have eight possible values: 0:~$0^\circ\!$, 1567 1:~$45^\circ\!$, 2:~$90^\circ\!$, 3:~$135^\circ\!$, 4:~$180^\circ\!$, 1568 5:~$225^\circ\!$, 6:~$270^\circ\!$, and 7:~$315^\circ\!$. 1569 1570|scope()| returns the maximal scope of the object. It is used in |@<Find 1571objects in vicinity of |objects[i]|@>| to find all objects in the vicinity of a 1572given on-object. 1573 1574@q;@> 1575 1576@c 1577typedef enum { @!hidden, @!visible, @!undecided } visibility; 1578 1579struct view_data { 1580 visibility in_view; 1581 double x,y; 1582 double radius, skip; 1583 visibility with_label; 1584 bool label_arranged; 1585 string label; 1586 double label_width, label_height, label_depth; 1587 bool on_baseline; 1588 color label_color; 1589 int label_angle; 1590 view_data() : in_view(undecided), x(DBL_MAX), y(DBL_MAX), radius(0.0), 1591 skip(params.label_skip), 1592 with_label(undecided), label_arranged(false), label(), 1593 label_width(0.0), label_height(0.0), 1594 label_depth(0.0), on_baseline(false), 1595 label_color(params.labelcolor), label_angle(0) { } 1596 void get_label_boundaries(double& left,double& right,double& top, 1597 double& bottom) const; 1598 double scope() const { return radius + skip + 1599 my_fmax(label_width,label_height) + 2.0*skip; } 1600 bool has_valid_coordinates() const { return x != DBL_MAX && y != DBL_MAX; } 1601 virtual double penalties_with(const double left, const double right, 1602 const double top, const double bottom, 1603 bool core = true) const; 1604}; 1605 1606@ This is the only structure that is not put into a container directly, but via 1607references. The reason is the virtual routine |penalties_with()|; I want to 1608use polymorphy. 1609 1610@c 1611typedef vector<view_data*> objects_list; 1612 1613@ Each label is stored in |view_data| by its dimensions, its |skip|, the 1614|radius| of the object itself, and the |label_angle|. While these quantities 1615are very convenient to use for the positioning of the label, they are pretty 1616unfortunate if you want to know which coordinates are occupied by the label in 1617order to find out possible overlaps. 1618 1619Therefore I calculate here the rectangular boundaries of a label. They are 1620stored in |left|, |right|, |top|, and |bottom| in screen centimetres. 1621 1622@c 1623void view_data::get_label_boundaries(double& left,double& right,double& top, 1624 double& bottom) const { 1625 const double origin_x = 1626 x + (radius + skip) * cos(M_PI_4 * double(label_angle)); 1627 const double origin_y = 1628 y + (radius + skip) * sin(M_PI_4 * double(label_angle)); 1629 switch (label_angle) { 1630 case 0: case 1: case 7: left = origin_x; break; 1631 case 2: case 6: left = origin_x - label_width / 2.0; break; 1632 case 3: case 4: case 5: left = origin_x - label_width; break; 1633 } 1634 right = left + label_width; 1635 switch (label_angle) { 1636 case 0: case 4: bottom = origin_y - 1637 (on_baseline? 0.0 : label_height / 2.0); break; 1638 case 1: case 2: case 3: bottom = origin_y; break; 1639 case 5: case 6: case 7: bottom = origin_y - label_height; break; 1640 } 1641 if (on_baseline) bottom -= label_depth; 1642 top = bottom + label_height; 1643} 1644 1645@ Every object of type |view_data| (or a descendant of it) must be able to 1646calculate the overlap of itself with a rectangle given by |left|, |right|, 1647|top|, and |bottom|. It must then create a penalty value out of it. Normally 1648this is just the overlap itself in square centimetres, like here. 1649 1650@c 1651double view_data::penalties_with(const double left, const double right, 1652 const double top, const double bottom, 1653 bool core) const { 1654 if (with_label == visible && label_arranged) { 1655 double left2, right2, top2, bottom2; 1656 get_label_boundaries(left2,right2,top2,bottom2); 1657 const double overlap_left = my_fmax(left, left2); 1658 const double overlap_right = my_fmin(right, right2); 1659 const double overlap_top = my_fmin(top, top2); 1660 const double overlap_bottom = my_fmax(bottom, bottom2); 1661 const double overlap_x = my_fdim(overlap_right, overlap_left); 1662 const double overlap_y = my_fdim(overlap_top, overlap_bottom); 1663 return overlap_x * overlap_y * params.penalties_label; 1664 } else return 0.0; 1665} 1666 1667@ The book claims that the following routines (well, without the |my_|) are 1668part of the {\mc GNU} \CEE/~Library version~2.2 beta. However, I didn't find 1669them. 1670 1671@q'@> 1672 1673@<Missing math routines@>= 1674inline double my_fmin(const double& x, const double& y) { 1675 return x < y ? x : y; 1676} 1677 1678inline double my_fmax(const double& x, const double& y) { 1679 return x > y ? x : y; 1680} 1681 1682inline double my_fdim(const double& x, const double& y) { 1683 return x > y ? x - y : 0.0; 1684} 1685 1686 1687@* Stars. The actual star data is -- like all other user defined data 1688structure in this program -- organised as a |struct| because it's too simple 1689for a |class|. |hd| is the Henry Draper Catalog number, |bs| is the Bright 1690Star Catalog number. |rectascension| is given in hours, |declination| in 1691degrees. |b_v| is the B$-$V brightness in magnitudes (equals 1692$m_{\hbox{\sevenrm phot}}-m_{\hbox{\sevenrm vis}}$) and thus contains colour. 1693|name| either contains the Bayer name (only the Greek letter and maybe an 1694exponent number), or, if this doesn't exist, the Flamsteed number as a string. 1695|spectral_class| is the complete spectral class, starting with |"F6"| or 1696whatever. 1697 1698@s star int 1699@s stars_list int 1700 1701@q;@> 1702 1703@c 1704struct star : public view_data { 1705 int hd, bs; 1706 double rectascension, declination; 1707 double magnitude; 1708 double b_v; 1709 int flamsteed; 1710 string name; 1711 string constellation; 1712 string spectral_class; 1713 star() : hd(0), bs(0), rectascension(0.0), declination(0.0), 1714 magnitude(0.0), b_v(0.0), flamsteed(0), name(""), 1715 spectral_class(""), view_data() { } 1716 virtual double penalties_with(const double left, const double right, 1717 const double top, const double bottom, 1718 bool core = true) const; 1719}; 1720 1721@ In order to find the penalties with a (labelled) star, I first calculate them 1722for the label itself, which may be |0.0|, in particular if |label_arranged| is 1723still |false|. Then I determine the rectangular overlap, just like in 1724|view_data::penalties_with()|. This is unfortunate, because stars are circles 1725and not rectangles. FixMe: This should be done justice to. 1726 1727@c 1728double star::penalties_with(const double left, const double right, 1729 const double top, const double bottom, 1730 bool core) const { 1731 double penalties = view_data::penalties_with(left, right, top, bottom, 1732 core); 1733 const double left2 = x - radius, right2 = x + radius, top2 = y + radius, 1734 bottom2 = y - radius; 1735 const double overlap_left = my_fmax(left, left2); 1736 const double overlap_right = my_fmin(right, right2); 1737 const double overlap_top = my_fmin(top, top2); 1738 const double overlap_bottom = my_fmax(bottom, bottom2); 1739 const double overlap_x = my_fdim(overlap_right, overlap_left); 1740 const double overlap_y = my_fdim(overlap_top, overlap_bottom); 1741 penalties += overlap_x * overlap_y * params.penalties_star; 1742 return penalties; 1743} 1744 1745 1746typedef vector<star> stars_list; 1747 1748@ I want to be able to read the star data records from a text format file. The 1749format of the input is a text stream with the following format. Each star 1750entry consists of four lines: \medskip 1751 1752\item{1.} A row with seven fields separated by whitespace: 1753\itemitem{--} Henry Draper Catalogue number (|int|, `|0|' if unknown), 1754\itemitem{--} \BSC/ number (|int|, `|0|' if unknown), 1755\itemitem{--} rectascension in hours (|double|), 1756\itemitem{--} declination in degrees (|double|), 1757\itemitem{--} visual brightness in magnitudes (|double|), 1758\itemitem{--} B$-$V brightness in magnitudes (|double|, `|99.0|' if unknown), 1759\itemitem{--} Flamsteed number (|int|, `|0|' if unknown). 1760\item{2.} The label (astronomical name) for the star, as a \LaTeX-ready string, 1761e.\,g.\ ``\.{\$\\alpha\$}'', ``\.{\$\\phi\^\{2\}\$}'', or simply 1762``\.{\$23\$}''. May be the empty string. 1763\item{3.} The astronomical abbreviation of the constellation. It must be all 1764uppercase. 1765\item{4.} The spectral class. It must start with the spectral class letter, 1766followed by the fraction digit, followed by the luminosity class as a Roman 1767number, e.\,g.~``\.{F5III}''. Anything may follow as in ``\.{K2-IIICa-1}'', 1768however the mandatory parts must not contain any whitespace. 1769 1770@c 1771istream& operator>>(istream& in, star& s) { 1772 in >> s.hd >> s.bs >> s.rectascension 1773 >> s.declination >> s.magnitude >> s.b_v 1774 >> s.flamsteed; 1775 char ch; 1776 do in.get(ch); while (in && ch != '\n'); 1777 getline(in,s.name); 1778 getline(in,s.constellation); 1779 getline(in,s.spectral_class); 1780 return in; 1781} 1782 1783@ Here I read a set of stars from a file with the name |filename|. The file 1784|stars_file| is a text file that consists solely of a list of |star|s. 1785 1786@c 1787void read_stars(stars_list& stars) { 1788 ifstream stars_file(params.filename_stars.c_str()); 1789 if (!stars_file) throw string("No stars file found: " 1790 + params.filename_stars); 1791 star current_star; 1792 stars_file >> current_star; 1793 while (stars_file) { 1794 current_star.label = string("\\Starname{") + current_star.name + '}'; 1795 stars.push_back(current_star); 1796 stars_file >> current_star; 1797 } 1798} 1799 1800 1801@* Nebulae. This is also used for stellar clusters of course. In the whole 1802program ``nebula'' denotes nebulae, galaxies, clusters, and other non-stellar 1803objects. 1804 1805@c 1806typedef enum { @!unknown, @!galaxy, @!emission, @!reflection, @!open_cluster, 1807 @!globular_cluster } nebula_kind; 1808 1809@ Since nebulae appear on the screen next to stars, and because they can have 1810labels, they are descendants of |view_data|, too. 1811 1812|ngc|, |ic|, and |messier| are of course the respective catalogue number. 1813Since it's silly that |ngc| and |ic| are defined, a value of `|0|' means that 1814the nebula is not part of the respective catalogue. |constellation| is a 1815three-character string with the name of the respective constellation in all 1816uppercase. |rectascension| is given in hours, |declination| in degrees. 1817|diameter_x| and |diameter_y| are the extent of the nebula in the horizonal and 1818the vertical direction and are given in degrees, |horizontal_angle| (in 1819degrees) is the angle between the horizontal axis of |diameter_x| and the 1820intersecting celestial rectascension circle. |magnitude| (in magnitudes) is 1821the {\it total\/} brightness (not the brightness density). 1822 1823@c 1824struct nebula : public view_data { 1825 int ngc, ic, messier; // |0| if not defined. 1826 string constellation; 1827 double rectascension, declination; 1828 double magnitude; 1829 double diameter_x, diameter_y; 1830 double horizontal_angle; 1831 nebula_kind kind; 1832 nebula() : ngc(0), ic(0), messier(0), constellation(), rectascension(0.0), 1833 declination(0.0), magnitude(0.0), diameter_x(0.0), 1834 diameter_y(0.0), horizontal_angle(0.0), kind(unknown) { } 1835 virtual double penalties_with(const double left,const double right, 1836 const double top, const double bottom, 1837 bool core = true) const; 1838}; 1839 1840typedef vector<nebula> nebulae_list; 1841 1842@ In order to find the penalties with a (labelled) nebula, I first calculate 1843them for the label itself, which may be |0.0|, in particular if 1844|label_arranged| is still |false|. Then I determine the rectangular overlap, 1845just like in |view_data::penalties_with()|. This is unfortunate, because nebulae 1846are circles and not rectangles. FixMe: This should be done justice to. 1847 1848@c 1849double nebula::penalties_with(const double left, const double right, 1850 const double top, const double bottom, 1851 bool core) const { 1852 double penalties = view_data::penalties_with(left, right, top, bottom, core); 1853 const double left2 = x - radius, right2 = x + radius, top2 = y + radius, 1854 bottom2 = y - radius; 1855 const double overlap_left = my_fmax(left, left2); 1856 const double overlap_right = my_fmin(right, right2); 1857 const double overlap_top = my_fmin(top, top2); 1858 const double overlap_bottom = my_fmax(bottom, bottom2); 1859 const double overlap_x = my_fdim(overlap_right, overlap_left); 1860 const double overlap_y = my_fdim(overlap_top, overlap_bottom); 1861 penalties += overlap_x * overlap_y * params.penalties_nebula; 1862 return penalties; 1863} 1864 1865@ As you can see, the file format for a nebulae file is very simple, because 1866there are no string fields with possible whitespace within them. It's just a 1867stream of fields separated by whitespace. |kind| is an |int|, and it's the 1868canonical translation of the |nebula_kind| to |int|. 1869 1870If the |horizontal_angle| is unknown, is must be~|720.0|. |diameter_y| must 1871always have a valid value, even if it's actually unknown; in this case it must 1872be equal to |diameter_y|. 1873 1874@q'@> 1875 1876@c 1877istream& operator>>(istream& in, nebula& n) { 1878 int kind; 1879 in >> n.ngc >> n.ic >> n.messier >> n.constellation >> n.rectascension 1880 >> n.declination >> n.magnitude >> n.diameter_x >> n.diameter_y 1881 >> n.horizontal_angle >> kind; 1882 n.kind = nebula_kind(kind); 1883 return in; 1884} 1885 1886@ Here I read a set of nebulae from a file with the name |filename|. The 1887format is just a stream of |nebula|e. 1888 1889@c 1890void read_nebulae(nebulae_list& nebulae) { 1891 ifstream nebulae_file(params.filename_nebulae.c_str()); 1892 nebula current_nebula; 1893 nebulae_file >> current_nebula; 1894 while (nebulae_file) { 1895 @<Create nebula label@>@; 1896 nebulae.push_back(current_nebula); 1897 nebulae_file >> current_nebula; 1898 } 1899} 1900 1901@ In order to create a \LaTeX-ready label, I first choose which catalog to use. 1902It is from the Messier, the \NGC/, and the \IC/ catalogue the first in which 1903the nebula appears, i.\,e.\ the first non-zero catalogue number. The catalogue 1904abbreviation itself is stored in |catalog|, whereas the |stringstream| |number| 1905contains the number within that catalogue. I just concatenate both to the 1906|label|. 1907 1908@<Create nebula label@>= 1909 string catalogue; 1910 stringstream number; 1911 if (current_nebula.messier > 0) { 1912 catalogue = "\\Messier{"; 1913 number << current_nebula.messier; 1914 } else if (current_nebula.ngc > 0) { 1915 catalogue = "\\NGC{"; 1916 number << current_nebula.ngc; 1917 } else if (current_nebula.ic > 0) { 1918 catalogue = "\\IC{"; 1919 number << current_nebula.ic; 1920 } else throw string("Invalid catalogue: \"") + catalogue + '"'; 1921 current_nebula.label = catalogue + number.str() + '}'; 1922 1923 1924@* Constellation boundaries. They are stored in an external file called 1925\.{constborders.dat}. 1926 1927{\sloppy Is is very convenient to have a special data type for a point in the 1928program that has created \.{constborders.dat}. In this program the advantage 1929is not so big but it's sensible to use the same data structures here.\par} 1930 1931@q'@> 1932 1933@c 1934struct point { 1935 double x,y; 1936 point(const double x, const double y) : x(x), y(y) { } 1937 point() : x(0.0), y(0.0) { } 1938}; 1939 1940@ An object of type |boundary| contains one stright line of a constellation 1941boundary. This consists of the two endpoints which come from the original 1942boundary catalog by Delporte of 1930, and zero or more interpolated points 1943calculated by Davenhall (1990). The interpolated points help to draw a curved 1944line where this is necessary. 1945 1946{\sloppy Every line usually belongs to exactly two constellations (of course). 1947They are stored in the field |constellations|. FixMe: There are boundaries 1948with only one owner. I don't know how this can happen.\par} 1949 1950@s boundary int 1951@s boundaries_list int 1952 1953@q'@> 1954 1955@c 1956struct boundary { 1957 vector<point> points; 1958 vector<string> constellations; 1959 bool belongs_to_constellation(const string constellation) const; 1960}; 1961 1962typedef vector<boundary> boundaries_list; 1963 1964@ In this routine I use only the first three letters of the constellation 1965abbreviation. The reason is that the original catalog uses |"SER1"| and 1966|"SER2"| for ``Serpent Caput'' and ``Serpent Cauda''. However, I see them as 1967one constellation. Be aware that this routine expects the constellation 1968abbreviation in uppercase. 1969 1970@c 1971bool boundary::belongs_to_constellation(const string constellation) const { 1972 for (int i = 0; i < constellations.size(); i++) 1973 if (constellations[i].substr(0,3) == constellation.substr(0,3)) 1974 return true; 1975 return false; 1976} 1977 1978@ Of course I need to be able to read the boundaries from 1979\.{constborders.dat}. The input stream is a text stream of the following 1980format. The set of celestial boundaries is stored as a set of elementary 1981lines without kinks. \medskip 1982 1983\item{1.} Number of points (|size|$_1$) in the line (|int|). 1984\item{2.} Repeated |size|$_1$ times: 1985\itemitem{--} rectascension of point in hours (|double|), 1986\itemitem{--} declination of point in degrees (|double|). 1987\item{3.} Number of constellations (|size|$_2$) touching this border line 1988 (|int|, should be be always |2|, however it isn't with current data). 1989\item{4.} Repeated |size|$_2$ times: 1990\itemitem{--} All uppercase astronomical abbreviation of the constellation. It 1991may distinguish between ``\.{SER1}'' and ``\.{SER2}'' for Serpens Caput and 1992Serpens Cauda. 1993 1994\medskip All fields are separated by whitespace. 1995 1996@q')@> 1997 1998@c 1999istream& operator>>(istream& in, boundary& p) { 2000 int size; 2001 in >> size; 2002 if (!in.good()) return in; 2003 p.points.resize(size); 2004 for (int i=0; i<size; i++) 2005 in >> p.points[i].x >> p.points[i].y; 2006 in >> size; 2007 p.constellations.resize(size); 2008 for (int i=0; i<size; i++) 2009 in >> p.constellations[i]; 2010 return in; 2011} 2012 2013@ Here I read a set of boundaries from a file. It's simply a list of 2014|boundary|'s. 2015 2016@c 2017void read_constellation_boundaries(boundaries_list& boundaries) { 2018 ifstream boundaryfile(params.filename_boundaries.c_str()); 2019 boundary current_boundary; 2020 boundaryfile >> current_boundary; 2021 while (boundaryfile) { 2022 boundaries.push_back(current_boundary); 2023 boundaryfile >> current_boundary; 2024 } 2025} 2026 2027@ As a big exception to the other classes, I don't derive |boundary| itself 2028from |view_data|, but its smaller brother, |boundary_atom|. In contrast to 2029|boundary|, |boundary_atom| only contains two points of the view frame, between 2030which a boundary line will be drawn. This is not totally accurate since 2031boundary lines are not totally straight which may cause problems at the poles. 2032However, it should be good enough. 2033 2034@s boundary_atom int 2035 2036@q'@> 2037 2038@c 2039struct boundary_atom : public view_data { 2040 point start, end; 2041 boundary_atom(point start, point end); 2042 virtual double penalties_with(const double left,const double right, 2043 const double top, const double bottom, 2044 bool core = true) const; 2045}; 2046 2047@ The nice thing about |boundary_atom| is that after it has been constructed, 2048it's finished. Nothing has to be changed any more because all is known in the 2049moment of construction. 2050 2051Since boundary atoms are only created when they are visible, I can set |in_view 2052= visible| etc. @q'@> 2053 2054@c 2055boundary_atom::boundary_atom(point start, point end) : start(start), end(end) { 2056 x = (start.x + end.x) / 2.0; 2057 y = (start.y + end.y) / 2.0; 2058 radius = hypot(end.x - start.x, end.y - start.y) / 2.0; 2059 radius *= M_PI_2; 2060 in_view = visible; 2061 with_label = hidden; 2062 skip = 0.0; 2063} 2064 2065@ The full algorithm that is used here is described in the |@<Definition of 2066|line_intersection()| for intersection of two lines@>|. Except for peculiar 2067cases there should be exact two intersections altogether, which means that it's 2068the same as with constellation lines. 2069 2070Objects of this type are created in |@<Create a |boundary_atom| for the 2071|objects|@>|. 2072 2073@q'@> 2074 2075@c 2076@<Definition of |line_intersection()| for intersection of two lines@>@;@# 2077 2078double boundary_atom::penalties_with(const double left,const double right, 2079 const double top, const double bottom, 2080 bool core) 2081 const { 2082 double intersection; 2083 point r(end.x - start.x, end.y - start.y); 2084 vector<point> intersection_points; 2085 if (line_intersection(left - start.x, r.x, start.y, r.y, 2086 bottom, top, intersection)) 2087 intersection_points.push_back(point(left, intersection)); 2088 if (line_intersection(right - start.x, r.x, start.y, r.y, 2089 bottom, top, intersection)) 2090 intersection_points.push_back(point(right, intersection)); 2091 if (line_intersection(top - start.y, r.y, start.x, r.x, 2092 left, right, intersection)) 2093 intersection_points.push_back(point(intersection, top)); 2094 if (line_intersection(bottom - start.y, r.y, start.x, r.x, 2095 left, right, intersection)) 2096 intersection_points.push_back(point(intersection, bottom)); 2097 if (start.x > left && start.x < right && start.y > bottom && start.y < top) 2098 intersection_points.push_back(start); 2099 if (end.x > left && end.x < right && end.y > bottom && end.y < top) 2100 intersection_points.push_back(end); 2101 if (intersection_points.empty()) return 0.0; 2102 if (intersection_points.size() != 2) { 2103 cerr << "pp3: Funny " << intersection_points.size() 2104 << "-fold constellation boundary intersecrtion." << endl; 2105 return 0.0; 2106 } 2107 const double length = hypot(intersection_points[1].x - 2108 intersection_points[0].x, 2109 intersection_points[1].y - 2110 intersection_points[0].y); 2111 return (core? 8.0 * params.penalties_boundary : 2112 0.5 * params.penalties_boundary_rim) / 72.27*2.54 * length; 2113} 2114 2115@* Constellation lines. This is not about the {\it boundaries}, but about the 2116connection lines between the main stars of a given constellation. They are 2117mere eye candy. I call their |struct| type ``connections'' to keep the name 2118unique and concise. 2119 2120A |connection| consists of |lines|. The point coordinates are screen 2121coordinates in centimetres. |from| and |to| are the star star and the end 2122star, given by their index in |stars|. 2123 2124Notice that |start| and |end| aren't defined before 2125|draw_constellation_lines()| has been called, {\it and\/} the constellation 2126line is actually visible. Then they contain the screen coordinates of the 2127start and the end point of the line. 2128 2129@s connection int 2130 2131@q'@> 2132 2133@c 2134struct connection : public view_data { 2135 point start, end; 2136 int from, to; 2137 connection(const int from, const int to) 2138 : from(from), to(to), start(), end() { in_view = visible; 2139 with_label = hidden; skip = 0.0; } 2140 virtual double penalties_with(const double left,const double right, 2141 const double top, const double bottom, 2142 bool core = true) const; 2143}; 2144 2145typedef vector<connection> connections_list; 2146 2147@ In order to calculate penalties, we need some sort of overlap. Since there 2148is no area overlap between a line and a rectangle, I need the overlapping line 2149length. 2150 2151This is the first part of the algorithm. It is actually pretty simple, however 2152hard to explain. We have two lines to intersect: One edge of the label 2153rectangle and the constellation line. The rectangle is given by |left|, 2154|right|, |top|, and |bottom| -- as usual. The constellation line is given by 2155their start point |start| and end point |end|. Or, in parameterised form: 2156% 2157$$\vec x = {\hbox{|start.x|} \choose \hbox{|start.y|}} + \lambda 2158{\hbox{|end.x|} - \hbox{|start.x|} \choose \hbox{|end.y|} - \hbox{|start.y|}}, 2159\qquad \lambda\in[0;1].$$ 2160% 2161The edge of the rectangle is given by (left edge as 2162example) 2163% 2164$$\eqalign{\left(\vec x - {\hbox{|left|} \choose 0}\right) \cdot 2165{1\choose0} &= 0 \cr \noalign{\vskip0.5ex} \Rightarrow\quad \hbox{|start.x|} + 2166\lambda (\hbox{|end.x|} - \hbox{|start.x|}) &= \hbox{|left|}\cr 2167\mathrel{\mathop\Leftrightarrow^{end.x - start.x \ne 0}\lambda = {\hbox{|left|} 2168- \hbox{|start.x|} \over \hbox{|end.x|} - \hbox{|start.x|}}}}$$ 2169% 2170with the 2171boundary condition $\hbox{|bottom|} < \hbox{|start.y|} + \lambda 2172(\hbox{|end.y|} - \hbox{|start.y|}) =: \hbox{|intersection|} < \hbox{|top|}$. 2173With the abbreviations/\hskip0ptassignments (also exemplarily for the `left' 2174case) 2175% 2176$$\eqalign{\hbox{|numerator|} &= \hbox{|left|} - \hbox{|start.x|},\cr 2177\hbox{|denominator|} &= \hbox{|end.x|} - \hbox{|start.x|},\cr 2178\hbox{|zero_point|} &= \hbox{|start_y|},\cr \hbox{|slope|} &= \hbox{|end.y|} - 2179\hbox{|start.y|},\cr \hbox{|min|} &= \hbox{|bottom|},\quad \hbox{|max|} = 2180\hbox{|top|}}$$ 2181% 2182we get the following routine for finding out whether a certain 2183label rectangle edge is intersected by the constellation line or not: 2184 2185@q')@> 2186 2187@<Definition of |line_intersection()| for intersection of two lines@>= 2188bool line_intersection(double numerator, double denominator, 2189 double zero_point, double slope, 2190 double min, double max, 2191 double& intersection) { 2192 if (denominator == 0) return false; 2193 const double lambda = numerator / denominator; 2194 intersection = zero_point + lambda * slope; 2195 return lambda > 0.0 && lambda < 1.0 && 2196 intersection > min && intersection < max; 2197} 2198 2199@ Now for the second part of the intersection algorithm. Here I apply the 2200preceding routing on all four label edges. I return an overlap as the penalty 2201value, however I pretend that the line has a width of 8\,pt. For an overlap 2202with the rim, it's only 0.5\,pt. 2203 2204@q'@> 2205 2206@c 2207double connection::penalties_with(const double left, const double right, 2208 const double top, const double bottom, 2209 bool core) const 2210{ 2211 double intersection; 2212 point r(end.x - start.x, end.y - start.y); 2213 vector<point> intersection_points; 2214 if (line_intersection(left - start.x, r.x, start.y, r.y, 2215 bottom, top, intersection)) 2216 intersection_points.push_back(point(left, intersection)); 2217 if (line_intersection(right - start.x, r.x, start.y, r.y, 2218 bottom, top, intersection)) 2219 intersection_points.push_back(point(right, intersection)); 2220 if (line_intersection(top - start.y, r.y, start.x, r.x, 2221 left, right, intersection)) 2222 intersection_points.push_back(point(intersection, top)); 2223 if (line_intersection(bottom - start.y, r.y, start.x, r.x, 2224 left, right, intersection)) 2225 intersection_points.push_back(point(intersection, bottom)); 2226 if (start.x > left && start.x < right && start.y > bottom && start.y < top) 2227 intersection_points.push_back(start); 2228 if (end.x > left && end.x < right && end.y > bottom && end.y < top) 2229 intersection_points.push_back(end); 2230 if (intersection_points.empty()) return 0.0; 2231 if (intersection_points.size() != 2) { 2232 cerr << "pp3: Funny " << intersection_points.size() 2233 << "-fold constellation line intersecrtion." << endl; 2234 return 0.0; 2235 } 2236 const double length = hypot(intersection_points[1].x - 2237 intersection_points[0].x, 2238 intersection_points[1].y - 2239 intersection_points[0].y); 2240 return (core? 8.0 * params.penalties_cline : 2241 0.5 * params.penalties_cline_rim) / 72.27*2.54 * length; 2242} 2243 2244@ I must be able to read a file which contains such data. Here, too, the text 2245file format is very simple: It's a list of constellation line paths separated 2246by `\.{;}'. A star name must be of the form $$\eqalign{\noalign{\hbox{{\it 2247Constellation}\SP{\it Flamsteed number}}} \noalign{\hbox{or}} 2248\noalign{\hbox{\.{HD}\SP{\it Henry-Draper Catalogue number}}}}$$ where {\it 2249Constellation\/} must be given as an all uppercase three letter abbreviation. 2250For example, \.{ORI\SP19} is $\alpha$~Ori (Rigel). 2251 2252The hash sign `\.{\#}' introduces comments that are ignored till end of line, 2253however they mustn't occur within a star. 2254 2255@c 2256void read_constellation_lines(connections_list& connections, 2257 const stars_list& stars) { 2258 ifstream file(params.filename_lines.c_str()); 2259 string from_catalogue_name, to_catalogue_name; 2260 int from_catalogue_index = 0, to_catalogue_index = 0; 2261 file >> to_catalogue_name; 2262 while (file) { 2263 if (to_catalogue_name[0] == '#') { // skip comment 2264 string dummy; 2265 getline(file,dummy); 2266 } else if (to_catalogue_name == ";") // start a new path 2267 from_catalogue_index = 0; 2268 else { 2269 file >> to_catalogue_index; 2270 if (from_catalogue_index > 0 && to_catalogue_index > 0) { 2271 @<Create one connection@>@; 2272 } 2273 from_catalogue_name = to_catalogue_name; 2274 from_catalogue_index = to_catalogue_index; 2275 } 2276 file >> to_catalogue_name; 2277 } 2278} 2279 2280@ In the loop I try to find the index within |stars| of the `from' star and the 2281`to'~star. 2282 2283@<Create one connection@>= 2284 int from_index = -1, to_index = -1; 2285 for (int i = 0; i < stars.size(); i++) { 2286 @<Test whether |stars[i]| is the `from' star@>@; 2287 @<Test whether |stars[i]| is the `to' star@>@; 2288 } 2289 if (from_index == -1 || to_index == -1) { 2290 stringstream error_message; 2291 error_message << "Unrecognised star in constellation lines: "; 2292 if (from_index == 0) 2293 error_message << from_catalogue_name << ' ' 2294 << from_catalogue_index; 2295 else 2296 error_message << to_catalogue_name << ' ' 2297 << to_catalogue_index; 2298 throw error_message.str(); 2299 } 2300 connections.push_back(connection(from_index, to_index)); 2301 2302@ Here I test whether the current star in the loop is the `from' star. Of 2303course only if I haven't found it already (|from_index == 0|). If apparently 2304both stars have been found already, I leave the loop immediately. 2305 2306@q;@> 2307 2308@<Test whether |stars[i]| is the `from' star@>= 2309@q'@> 2310 if (from_index == -1) 2311 if (from_catalogue_name == "HD") { 2312 if (stars[i].hd == from_catalogue_index) from_index = i; 2313 } else { 2314 if (from_catalogue_name == stars[i].constellation) 2315 if (stars[i].flamsteed == from_catalogue_index) 2316 from_index = i; 2317 } else if (to_index != -1) break; 2318 2319@ Perfectly analogous to |@<Test whether |stars[i]| is the `from' star@>|. 2320 2321@q'@> 2322 2323@<Test whether |stars[i]| is the `to' star@>= 2324@q'@> 2325 if (to_index == -1) 2326 if (to_catalogue_name == "HD") { 2327 if (stars[i].hd == to_catalogue_index) to_index = i; 2328 } else { 2329 if (to_catalogue_name == stars[i].constellation) 2330 if (stars[i].flamsteed == to_catalogue_index) 2331 to_index = i; 2332 } else if (from_index != -1) break; 2333 2334 2335@* The Milky Way. The file is a text file as usual with the following 2336structure, everything separated by whitespace: \medskip 2337 2338\item{1.} The maximal ($={}$equatorial) diagonal half distance of two pixels in 2339degrees (|double|). This value is used as the |radius| for the milky way 2340`pixels'. Of course it must be the minimal radius for which there are no gaps 2341between the pixels. 2342 2343\item{2.} The pixels themselves with two |double|s and one |int| each: 2344\itemitem{--} The rectascension in hours. 2345\itemitem{--} The declination in degrees. 2346\itemitem{--} The grey value of the pixel from $1$ to~$255$. Zero is not used 2347because zero-value pixels are not included into the data file anyway. 2348 2349In \PPTHREE/'s standard distribution, this file was produced with the helper 2350program \.{milkydigest.cc}, and the original Milky Way bitmap was photographed 2351and compiled by \pdfURL{Axel 2352Mellinger}{http://home.arcor-online.de/axel.mellinger/}. 2353 2354|pixels| is a |vector<vector<point> >|. The outer (first) index is the grey 2355 value, and for every grey value there is an inner vector (second index) with a 2356 list of all points (rectascension, declination) that have this value. This 2357 construction makes the drawing in Ghostview looking nice, but more importantly 2358 it reduces the number of colour change commands in the \LaTeX\ file. 2359 Unfortunately this doesn't reduce pool size usage. 2360 2361@<Reading the milkyway into |pixels|@>= 2362 ifstream file(params.filename_milkyway.c_str()); 2363 double radius; 2364 file >> radius; 2365 double rectascension, declination, x, y; 2366 int pixel; 2367 const double cm_per_grad = 1.0 / 2368 (mytransform.get_rad_per_cm() * 180.0 / M_PI); 2369 radius *= cm_per_grad / 2.54 * 72.27; 2370 file >> rectascension >> declination >> pixel; 2371 while (file) { 2372 if (mytransform.polar_projection(rectascension, declination, x,y)) 2373 pixels[pixel].push_back(point(x,y)); 2374 file >> rectascension >> declination >> pixel; 2375 } 2376 2377 2378@* Colour. It's very convenient to have a unified data structure for all 2379colours that appear in this program. Its internal structure is trivial, and I 2380only support the \RGB/ colour model. The only complicated thing is |name| 2381here. I need it because of \PS/Tricks' way to activate colours: They must get 2382names first. I could get rid of it if I called all colours e.\,g.\ 2383``\.{tempcolor}'' or ``\.{dummycolor}'' and activated them at once. But this 2384is not necessary. 2385 2386@s color int 2387 2388@<Define |color| data structure@>= 2389struct color { 2390 double red, green, blue; 2391 string name; 2392 color(string name, double red, double green, double blue) 2393 : red(red), green(green), blue(blue), name(name) { } 2394 color(double red, double green, double blue) 2395 : red(red), green(green), blue(blue), name() { } 2396 void set(ostream& out) const; 2397}; 2398 2399@ This routine is used when the name of the colour is not necessary, because 2400it's only needed locally. This is the case for text labels and the Milky Way 2401dots. 2402 2403@q'@> 2404 2405@c 2406void color::set(ostream& out) const { 2407 out << "\\newrgbcolor{dummycolor}{" << red << ' ' << green << ' ' << blue 2408 << "}\\dummycolor\n \\psset{linecolor=dummycolor}%\n"; 2409} 2410 2411@ Both output and input of |color|s is asymmetric: When I {\it read\/} them I 2412assume that I do it from an input script. Then it's a mere sequence of the 2413three colour values. 2414 2415@q'@> 2416 2417@c 2418istream& operator>>(istream& in, color& c) { 2419 in >> c.red >> c.green >> c.blue; 2420 if (!in @/ 2421 || c.red < 0.0 || c.red > 1.0 @/ 2422 || c.green < 0.0 || c.green > 1.0 @/ 2423 || c.blue < 0.0 || c.blue > 1.0) 2424 throw string("Invalid RGB values in input script"); 2425 return in; 2426} 2427 2428@ But when I {\it write\/} them, I assume that I do it into a \LaTeX\ file with 2429\PS/Tricks package activated. Then I deploy a complete \.{\\newrgbcolor} 2430command. 2431 2432@c 2433ostream& operator<<(ostream& out, const color& c) { 2434 if (c.name.empty()) throw string("Cannot write unnamed color to stream"); 2435 out << "\\newrgbcolor{" << c.name << "}{" << c.red << ' ' << c.green 2436 << ' ' << c.blue << "}%\n"; 2437 return out; 2438} 2439 2440@ This routine is hitherto only used when drawing the milky way. It helps to 2441find a colour between the two extremes |c1| and~|c2|. The value of |x| is 2442always between $0$ and~$1$ and denotes the point on the way between |c1| and 2443|c1| in the \RGB/ colour space where the new colour should be. I interpolate 2444linearly. In order to create a new colour object, I need a |new_name| for it, 2445too. 2446 2447@c 2448color interpolate_colors(const double x, const color c1, const color c2, 2449 const string new_name = "") { 2450 if (x < 0.0 || x > 1.0) throw string("Invalid x for color interpolation"); 2451 const double y = 1.0 - x; 2452 return color(new_name, 2453 y * c1.red + x * c2.red, 2454 y * c1.green + x * c2.green, 2455 y * c1.blue + x * c2.blue); 2456} 2457 2458 2459@q@@** Ephemerides.@> 2460 2461 2462@** Drawing the chart. This is done in two steps. First the lines and the 2463celestial objects are printed. During this phase a lot of labels may 2464accumulate. They cannot be drawn at that phase, because the arrangement with 2465minimal overlaps can only be calculated when all are known. 2466 2467Therefore I have to fill a container called |objects| which contains elements 2468of type |view_data|. The part of each |star| or |nebula| or whatever that is 2469|view_data| is appended to |objects|, if and only if the object is visible in 2470the view frame. The typical command for that is 2471$$\hbox{|objects.push_back(&stars[i]);|}$$ (here for stars). 2472Please notice that it is {\it not\/} important whether or not the respective 2473object bears a label. Its data is needed in any case. 2474 2475In the second phase I arrange and print the labels. 2476 2477@* Coordinate transformations. They are done by the class |transformation|. 2478|width| and |height| contain the view frame dimensions in centimetres. 2479|rad_per_cm| is the resolution. 2480 2481The $3\times3$ matrices |a| and |a_unscaled| are rotation matrices for the 2482transformation in cartesian space from the equatorial system into the azimuthal 2483system, where the center of the view frame is the $z$ axis. While |a_unscaled| 2484refers to a celestial sphere with radius~$1$, |a| contains the additional 2485scaling for the actual centimetres on the paper. 2486 2487@s transformation int 2488 2489@c 2490class transformation { 2491 double width, height, rad_per_cm; 2492 double a[3][3], a_unscaled[3][3]; 2493 inline double stretch_factor(double z) const; 2494public: 2495 transformation(const double rectascension, 2496 const double declination, 2497 const double width, const double height, 2498 const double grad_per_cm); 2499 bool polar_projection(const double rectascension, const double declination, 2500 double& x, double& y) const; 2501 double get_rad_per_cm() const @+ { @+ return rad_per_cm; @+ } 2502}; 2503 2504@ Starting point is the parallel projection of a celestial unit sphere on the 2505$x$-$y$ plane. This projection looks like a sky globus seen from far away with 2506a strong zoom objective. 2507 2508It is the starting point because it simply is a necessary intermediate step: 2509All celestial positions given in polar coordinates ({\it rectascension}, {\it 2510declination\/}) must be transformed to cartesian coordinates in order to apply 2511a rotation on them for having the center of the view frame in the center of the 2512coordinate system. From there it's a trivial step to the parallel projection. 2513 2514But it squeezes the rim areas too much, which I don't like. 2515 2516By |stretch| I mean the radial stretch factor that should relieve this 2517distortion. If |stretch| was |1.0|, we would get the mentioned parallel 2518projection. 2519 2520\def\frac#1#2{{#1\over#2}} 2521 2522Therefore I stretch the plot here so that this effect is minimised. The result 2523is the equidistant azimuthal projection that can be described best if you 2524imagine a plot with its center exactly on the north pole: All circles of equal 2525declination have then the same distance from each other. So the plot keeps its 2526circular form. The {\it radial\/} scale is constant everywhere and equal to 2527|grad_per_cm|. Perpendicular to that, the scale is decreasing from 2528|grad_per_cm| (center) to $2/\pi\cdot{}$|grad_per_cm| (border). The exact 2529relation is $$\eqalignno{{\it scale}_\parallel &= \hbox{|grad_per_cm|},\cr {\it 2530scale}_\perp &= \hbox{|grad_per_cm|} \cdot {r \over 2531\arccos\sqrt{1-r^2}},\quad\hbox{and thus}\cr \noalign{\vskip0.5ex} {\it 2532stretch} &= {\arccos\sqrt{1-r^2} \over r}. &(1)}$$ $r$ is simply the distance 2533from the center of the plot and it is $1$ on the border. But I don't have $r$, 2534I have $z$. I could to the substitution $r = \sqrt{1-z^2}$ leading to a very 2535badly converging Taylor series, but much better is $\tilde z = 1 - z$ and to 2536use a Taylor series of $${\it stretch} = {\arccos (1-\tilde z) \over \sqrt{1- 2537(1-\tilde z)^2}}.$$ Maple\,V says $${\it stretch} = 1+1/3\,\tilde 2538z+2/15\,{\tilde z}^{2}+{\frac {2}{35}}\,{\tilde z}^{3} + {\frac 2539{8}{315}}\,{\tilde z}^ {4} + {\frac{8}{693}}\,{\tilde z}^{5} + {\cal O}({\tilde 2540z}^{11/2}).$$ This is good enough (error less than 1\,\%). There are two 2541alternatives: 2542 2543\medskip \item{1.} Use the Tayor expansion of~(1) directly, because this 2544expansion converges very quickly, especially for the center-near regions. This 2545would mean to calculate $r(z)$ for every point, but this shouldn't be too 2546costly. \item{2.} Transform back in spherical coordinates, re-interpret them 2547as planar polar coordinates and transform them to planar cartesian. Probably 2548too difficult. \medskip 2549 2550Why not calculating~(1) (maybe with a subsitution) directly without series 2551expansion? First, it may be too costly. But secondly, I don't like that in 2552the particularly interesting region close to the center of the view frame both 2553numerator and denominator get close to $0$, and eventually they do reach it. 2554Maybe I'm paranoid, but I don't like that. 2555 2556\def\zt{\tilde z} 2557 2558@s zt TeX 2559 2560@q'@> 2561 2562@c 2563inline double transformation::stretch_factor(const double z) const { 2564 const double zt = 1.0 - z; 2565 return 1.0 + zt * 2566 ( 1.0/3.0 + zt * 2567 ( 2.0/15.0 + zt * 2568 ( 2.0/35.0 + zt * 2569 ( 8.0/315.0 + zt * 2570 ( 8.0/693.0 ))))); 2571} 2572 2573@ This is not only the constructor for |transformation|, it is also the 2574initialiser for the whole transformation. As much work as possible is this 2575routine, in order to keep calculations easy in the function 2576|polar_projection()| which will be called hundreds if not thousands of times. 2577 2578|rectascension| is given in hours, together with |declination| it's the center 2579of the desired view frame. |width| and |height| give its dimensions in 2580centimetres, |grad_per_cm| is the resulting resolution in the center. 2581$$\hbox{|a_unscaled|} = \pmatrix{\sin\varphi & \cos\varphi & 0\cr 2582\cos\varphi\cos\alpha & -\sin\varphi\cos\alpha & \sin \alpha\cr 2583-\cos\varphi\sin\alpha & \sin\varphi\sin\alpha & \cos\alpha}.$$ 2584 2585@q'@> 2586 2587@c 2588transformation::transformation(const double rectascension, 2589 const double declination, 2590 const double width, const double height, 2591 const double grad_per_cm) { 2592 const double phi = - (rectascension + 12) * 15.0 * M_PI / 180.0; 2593 const double delta = declination * M_PI / 180.0; 2594 const double alpha = - delta + M_PI_2; 2595 rad_per_cm = grad_per_cm * M_PI / 180.0; 2596 a_unscaled[0][0] = sin(phi); 2597 a_unscaled[0][1] = cos(phi); 2598 a_unscaled[0][2] = 0.0; 2599 a_unscaled[1][0] = cos(phi) * cos(alpha); 2600 a_unscaled[1][1] = -sin(phi) * cos(alpha); 2601 a_unscaled[1][2] = sin(alpha); 2602 a_unscaled[2][0] = -cos(phi) * sin(alpha); 2603 a_unscaled[2][1] = sin(phi) * sin(alpha); 2604 a_unscaled[2][2] = cos(alpha); 2605 for (int i = 0; i < 3; i++) 2606 for (int j = 0; j < 3; j++) 2607 a[i][j] = a_unscaled[i][j] / rad_per_cm; 2608 transformation::width = width; 2609 transformation::height = height; 2610} 2611 2612@ Here I transform the equatorial position $(\hbox{|rectascension|}, 2613\hbox{|declination|})$ to the cartesian position $(x,y)$. The resulting 2614cartesian positions represents an azimuthal equidistant projection with the 2615center of the view frame (see |rectascension| and |declination| in the 2616constructor of |transformation|). 2617 2618It returns |true| if the resulting point is within the view frame, otherwise 2619|false|. 2620 2621@c 2622bool transformation::polar_projection(const double rectascension, 2623 const double declination, 2624 double& x, double& y) const { 2625 const double phi = rectascension * 15.0 * M_PI / 180.0; 2626 const double delta = declination * M_PI / 180.0; 2627 const double cos_delta = cos(delta); 2628 2629 const double x0 = cos_delta * cos(phi); 2630 const double y0 = cos_delta * sin(phi); 2631 const double z0 = sin(delta); 2632 2633 const double z1 = 2634 a_unscaled[2][0] * x0 + a_unscaled[2][1] * y0 + a_unscaled[2][2] * z0; 2635 if (z1 < -DBL_EPSILON) return false; 2636 const double stretch = stretch_factor(z1); 2637 const double x1 = a[0][0] * x0 + a[0][1] * y0; 2638 const double y1 = a[1][0] * x0 + a[1][1] * y0 + a[1][2] * z0; 2639 x = x1 * stretch + width / 2.0; 2640 y = y1 * stretch + height / 2.0; 2641 if (x < 0.0 || x > width || y < 0.0 || y > height) return false; 2642 return true; 2643} 2644 2645 2646@* Label organisation. Without labels, star charts are not very useful. But 2647labels mustn't overlap, and they should not overlap with other chart elements 2648such as star circles or nebula circles. Here I try to develop a simple 2649algorithm that is able to avoid most of these problems. There are two main 2650routines here: |arrange_labels()| and |print_labels()|. 2651 2652|arrange_labels()| modifies the |label_angle| field in each celestial object 2653in order to avoid any overlap with other objects, namely other labels, stars, 2654or nebulae. It does so by testing all eight values for |label_angle| and 2655calculating a |penalty| value for each of them. This |penalty| is 2656$$\hbox{|penalty|} = \hbox{\it overlap}_{\hbox{\sevenrm labels}} + \hbox{\it 2657overlap}_{\hbox{\sevenrm objects}} + \hbox{\it penalty}_{\hbox{\sevenrm 2658lines}}.$$ 2659 2660|print_labels()| actually generates the \LaTeX\ code for printing them. 2661 2662@q'@> 2663 2664@ First the routine that actually calculates the overlap. It simply finds the 2665common rectangular area in squared centimetres. Both rectangles are given by 2666their boundaries, |left1|, |right1|, |top1|, |bottom1| enclose the first 2667rectangle, |left2|, |right2|, |top2|, |bottom2| the second. 2668 2669@c 2670double calculate_overlap(double left1, double right1, double top1, 2671 double bottom1, double left2, double right2, 2672 double top2, double bottom2) { 2673 const double overlap_left = my_fmax(left1, left2); 2674 const double overlap_right = my_fmin(right1, right2); 2675 const double overlap_top = my_fmin(top1, top2); 2676 const double overlap_bottom = my_fmax(bottom1, bottom2); 2677 const double overlap_x = my_fdim(overlap_right, overlap_left); 2678 const double overlap_y = my_fdim(overlap_top, overlap_bottom); 2679 return overlap_x * overlap_y; 2680} 2681 2682@ Nor for a structure with the real dimensions in centimetres of all possible 2683labels. They are read from the file \.{labeldims.dat} and stored in a 2684|dimensions_list|. 2685 2686@s dimensions_list int 2687@s dimension int 2688@q'@> 2689 2690@c 2691struct dimension { 2692 double width, height, depth; 2693 dimension() : width(0.0), height(0.0), depth(0.0) { } 2694 dimension(const dimension& d) : width(d.width), height(d.height), 2695 depth(d.depth) { } 2696}; 2697 2698typedef map<string,dimension> dimensions_list; 2699 2700@ Now we re-arrange the labels, namely |objects[i].with_label| for all~|i|. 2701For efficiency, I first find all neighbours of the on-object and do all the 2702following work only with them. In the inner |k|-loop I test all possible 2703|label_angle|s and calculate their |penalty|. 2704 2705If a label leaps out of the view frame, this adds to |penalty| the gigantic 2706value of |10000.0|. 2707 2708@c 2709@<|create_preamble()| for writing the \LaTeX\ preamble@>@#@; 2710@<Helping routines for nebulae labels@>@#@; 2711 2712void arrange_labels(objects_list& objects, dimensions_list& dimensions) { 2713 objects_list vicinity; 2714 @<Set label dimensions@>@; 2715 for (int i = 0; i < objects.size(); i++) { 2716 vicinity.clear(); 2717 if (objects[i]->in_view == visible && objects[i]->with_label == visible 2718 && !objects[i]->label_arranged) { 2719 @<Find objects in vicinity of |objects[i]|@>@; 2720 double best_penalty = DBL_MAX; 2721 int best_angle = 0; 2722 for (int k = 0; k < 8; k ++) { 2723 objects[i]->label_angle = k; 2724 double on_object_left, on_object_right, on_object_top, 2725 on_object_bottom; 2726 objects[i]-> 2727 get_label_boundaries(on_object_left, on_object_right, 2728 on_object_top, on_object_bottom); 2729 double penalty = 0.0; 2730 double rim_width = 2.0 * objects[i]->skip; 2731 for (int j = 0; j < vicinity.size(); j++) { 2732 penalty += vicinity[j]-> 2733 penalties_with(on_object_left, on_object_right, 2734 on_object_top, on_object_bottom) + @| 2735 vicinity[j]-> 2736 penalties_with(on_object_left - rim_width, 2737 on_object_right + rim_width, 2738 on_object_top + rim_width, 2739 on_object_bottom - rim_width, 2740 false) * params.penalties_rim; 2741 } 2742 if (on_object_left < 0.0 || on_object_bottom < 0.0 || 2743 on_object_right > params.view_frame_width || 2744 on_object_top > params.view_frame_height) 2745 penalty += 10000.0; 2746 if (penalty < best_penalty) { 2747 best_penalty = penalty; 2748 best_angle = k; 2749 } 2750 } 2751 if (!objects[i]->label.empty()) 2752 if (best_penalty < 0.4 * params.penalties_threshold * 2753 objects[i]->label_height * objects[i]->label_width) { 2754 objects[i]->label_angle = best_angle; 2755 objects[i]->label_arranged = true; 2756#ifdef DEBUG 2757 stringstream penalty; 2758 penalty.precision(2); 2759 penalty << " " << best_penalty << " (" 2760 << best_penalty / 2761 (objects[i]->label_height * objects[i]->label_width) 2762 * 100 << "%)"; 2763 // |objects[i]->label += penalty.str();| 2764 cerr << "pp3DEBUG: Object " << objects[i]->label << ' '; 2765 const star* s = dynamic_cast<star*>(objects[i]); 2766 if (s) cerr << s->constellation; 2767 cerr << " has penalty of" << penalty.str() << endl; 2768#endif 2769 } else { 2770 objects[i]->with_label = hidden; 2771 objects[i]->label_arranged = true; 2772 } 2773 } 2774 } 2775} 2776 2777@ All objects in the vicinity of |objects[i]| eventually end up in the |vector| 2778|vicinity|. Here I fill this structure. I use a very rough guess for finding 2779the neighbours, so there will probably be too many of them, but it makes 2780calculation much easier. 2781 2782The practical thing is that neighbouring objects are ordered in increasing 2783brightness in star data file, which means that lables of bright stars are 2784arranged first, and labels of fainter stars must cope with these positions. 2785 2786Of course, it's guaranteed that |objects[i]| is not part of its vicinity. 2787 2788@q'@> 2789 2790@<Find objects in vicinity of |objects[i]|@>= 2791 const double on_object_scope = objects[i]->scope(); 2792 for (int j = 0; j < objects.size(); j++) { 2793 if (i != j && objects[j]->in_view == visible) { 2794 const double distance = 2795 hypot(objects[i]->x - objects[j]->x, 2796 objects[i]->y - objects[j]->y); 2797 if (distance < on_object_scope + objects[j]->scope() && 2798 @<Condition to exclude double stars and such@>) 2799 vicinity.push_back(objects[j]); 2800 } 2801 } 2802 2803@ @<Condition to exclude double stars and such@>= 2804(distance > objects[j]->skip || 2805 (objects[j]->with_label == visible && !objects[j]->label.empty())) 2806 2807@ Finally I print out all labels by generation \LaTeX\ code from any of them. 2808I do that by calculating the coordinates in centimetres of the {\it bottom 2809left\/} corner of the label box, and placing the \TeX\ box there. This \TeX\ 2810box lies within another one with zero dimensions in order to keep the point of 2811origin (bottom left of the view frame) intact. 2812 2813@c 2814void print_labels(const objects_list& objects) { 2815 for (int i = 0; i < objects.size(); i++) 2816 if (objects[i]->in_view == visible && objects[i]->with_label == visible 2817 && objects[i]->label_arranged) { 2818 double left, right, top, bottom; 2819 objects[i]->get_label_boundaries(left,right,top,bottom); 2820 if (left < 0.0 || bottom < 0.0 || right > params.view_frame_width 2821 || top > params.view_frame_height) continue; 2822 2823 objects[i]->label_color.set(OUT); 2824 OUT << "\\hbox to 0pt{"; 2825 OUT << "\\hskip" << left << "cm"; 2826 OUT << "\\vbox to 0pt{\\vss\n \\hbox{\\Label{"; 2827 OUT << objects[i]->label; 2828 OUT << "}}\\vskip" << bottom << "cm"; 2829 OUT << "\\hrule height 0pt}\\hss}%\n"; 2830#ifdef DEBUG 2831 OUT << "\\psframe[linewidth=0.1pt](" << left << ',' << bottom 2832 << ")(" << right << ',' << bottom+objects[i]->label_depth << ")%\n"; 2833 OUT << "\\psframe[linewidth=0.3pt](" << left << ',' << bottom 2834 << ")(" << right << ',' << top << ")%\n"; 2835#endif 2836 } 2837} 2838 2839@ Here I read label dimensions from a text file. The format is very simple: 2840\medskip 2841 2842\item{1.} The \LaTeX\ representation of the label on a line of its own. 2843\item{2.} In the following line, width, height, and depth of the label in 2844centimetres (both |double|), separated by whitespace. 2845 2846\medskip This is repeated for every data record. 2847 2848@c 2849void read_label_dimensions(dimensions_list& dimensions) { 2850 ifstream file(params.filename_dimensions.c_str()); 2851 string name,dummy; 2852 getline(file,name); 2853 while (file) { 2854 file >> dimensions[name].width >> dimensions[name].height 2855 >> dimensions[name].depth; 2856 getline(file,dummy); // Read the |'\n'| 2857 getline(file,name); 2858 } 2859} 2860 2861@*1 Determining label dimensions. {\sloppy Here I go through all |objects| and 2862set the |label_width|, |label_height|, and |label_depth| which have been zero 2863so far. It may happen that a label is not found (possibly because |dimensions| 2864is totally empty because no label dimensions file could be found). In this 2865case I call |recalculate_dimensions()| to get all labels recalculated via an 2866extra \LaTeX\ run.\par} 2867 2868|dimensions_recalculated| is |true| if |recalculate_dimensions()| has been 2869 called and thus one can assume that all needed labels are now available. It 2870 is merely to remove unnecessary tests and make the procedure faster. 2871 2872The |throw| command should never happen. It means an internal error. 2873 2874@<Set label dimensions@>= 2875bool dimensions_recalculated = false; 2876for (int i = 0; i < objects.size(); i++) { 2877 view_data* current_object = objects[i]; 2878 if (current_object->with_label == visible) { 2879 if (!dimensions_recalculated && 2880 dimensions.find(current_object->label) == dimensions.end()) { 2881 recalculate_dimensions(dimensions,objects); 2882 dimensions_recalculated = true; 2883 if (dimensions.find(current_object->label) == dimensions.end()) 2884 throw string("Update of label dimensions file failed: \"") + 2885 current_object->label + "\" not found"; 2886 } 2887 current_object->label_width = dimensions[current_object->label].width; 2888 current_object->label_height = 2889 dimensions[current_object->label].height; 2890 current_object->label_depth = dimensions[current_object->label].depth; 2891 } 2892} 2893 2894 2895@ When \PPTHREE/ is started it reads a file usually called \.{labeldimens.dat} 2896in order to know width, height, and depth (in centimetres) of all labels. This 2897is vital for the penalty (i.\,e.\ overlap) calculations. But it may be that a 2898label that you want to include can't be found in this file, or you have deleted 2899it because you've changed the \LaTeX\ preamble of the output (i.\,e.\ the 2900fonts). In these cases \PPTHREE/ automatically creates a new one. It is then 2901used in the following runs to save time. 2902 2903Here I do this. First I store all label names (not only the missing!)\ in 2904|required_names|. I assure that every label occurs only once. 2905 2906Then I create a |temp_file| which is a \LaTeX\ file that -- if sent through 2907\LaTeX\ -- is able to create another temporary file called |raw_labels_file|. 2908This is read and stored directly in |dimensions| (where it belongs to 2909naturally). 2910 2911Finally, a new updated |cooked_labels_file| (aka \.{labeldimens.dat}) is 2912created. 2913 2914\medskip 2915By the way, I am forced to use {\it two\/} temporary files. It is impossible 2916to let the \LaTeX\ file create directly the file \.{labeldimens.dat}. The 2917reason are the label string: They may contain characters that have a special 2918meaning in \LaTeX, and I'm unable to avoid any tampering. 2919 2920@q'@> 2921 2922@<Helping routines for nebulae labels@>= 2923void recalculate_dimensions(dimensions_list& dimensions, 2924 const objects_list& objects) 2925{ 2926 list<string> required_names; 2927 for (int i = 0; i < objects.size(); i++) { 2928 const string current_name = objects[i]->label; 2929 if (!current_name.empty()) required_names.push_back(current_name); 2930 } 2931 required_names.unique(); 2932 2933 ofstream temp_file("pp3temp.tex"); 2934 create_preamble(temp_file); 2935 temp_file << "\n\\begin{document}\n" 2936 << "\\newwrite\\labelsfile\n" 2937 << "\\catcode`\\_=11 % for underscores in the filename\n" 2938 << "\\immediate\\openout\\labelsfile=pp3temp.dat\n" 2939 << "\\catcode`\\_=8\n"; 2940 list<string>::const_iterator p = required_names.begin(); 2941 while (p != required_names.end()) 2942 temp_file << "\\setbox0 = \\hbox{\\Label{" << *(p++) 2943 << "}}\n \\immediate\\write\\labelsfile{" 2944 "\\the\\wd0s \\the\\ht0s \\the\\dp0s}\n"; 2945 temp_file << "\\immediate\\closeout\\labelsfile\n\\end{document}\n"; 2946 temp_file.close(); 2947 string commandline("latex pp3temp"); 2948 if (params.filename_output.empty()) 2949 commandline += " > pp3dump.log"; 2950 if (system(commandline.c_str()) != 0) 2951 throw string("Label dimensions calculations: LaTeX call failed: ") 2952 + commandline; 2953 2954 ifstream raw_labels_file("pp3temp.dat"); 2955 p = required_names.begin(); 2956 while (p != required_names.end()) { 2957 string current_width, current_height, current_depth; 2958 string current_name; 2959 current_name = *(p++); 2960 raw_labels_file >> current_width >> current_height >> current_depth; 2961 current_width.substr(0,current_width.length() - 3); 2962 current_height.substr(0,current_height.length() - 3); 2963 current_depth.substr(0,current_depth.length() - 3); 2964 dimensions[current_name].width = strtod(current_width.c_str(), 0) 2965 / 72.27 * 2.54; 2966 dimensions[current_name].height = strtod(current_height.c_str(), 0) 2967 / 72.27 * 2.54; 2968 dimensions[current_name].depth = strtod(current_depth.c_str(), 0) 2969 / 72.27 * 2.54; 2970 dimensions[current_name].height += dimensions[current_name].depth; 2971 } 2972 2973 @<Write updated \.{labeldimens.dat} file@>@; 2974} 2975 2976@ Here I create the new, updated |cooked_labels_file| (aka 2977\.{labeldimens.dat}). So all already calculated dimensions can be used for the 2978following runs. 2979 2980@<Write updated \.{labeldimens.dat} file@>= 2981 ofstream cooked_labels_file("labeldimens.dat"); 2982 cooked_labels_file.setf(ios::fixed); 2983 cooked_labels_file.precision(5); 2984 dimensions_list::const_iterator q = dimensions.begin(); 2985 while (q != dimensions.end()) { 2986 string current_name; 2987 dimension current_dimension; 2988 current_name = q -> first; 2989 current_dimension = (q++) -> second; 2990 cooked_labels_file << current_name << '\n' 2991 << current_dimension.width << ' ' 2992 << current_dimension.height << ' ' 2993 << current_dimension.depth 2994 << '\n'; 2995 } 2996 2997 2998@*1 User labels. The user should be able to insert arbitaray text into the 2999chart. The code for this is provided here. The data type of them, |text|, is 3000of course a classical derivative of |view_data|. It only holds the celestial 3001coordinates and the text (\LaTeX) string of the label. 3002 3003@s text int 3004 3005@c 3006struct text : public view_data { 3007 string contents; 3008 double rectascension, declination; 3009 text(string t, double r, double d, color c, int angle, bool on_baseline); 3010}; 3011 3012typedef list<text> texts_list; 3013 3014@ In the constructor I modify the values of some |view_data| elements, because 3015a |text| is a special label. For example it mustn't be arranged, because it is 3016already. And it gets another colour (if wanted). 3017 3018@q'@> 3019 3020@c 3021text::text(string t, double r, double d, color c, int angle, bool on_baseline) 3022 : contents(t), rectascension(r), declination(d) { 3023 label = string("\\TextLabel{") + t + '}'; 3024 with_label = visible; 3025 label_angle = angle; 3026 view_data::on_baseline = on_baseline; 3027 label_arranged = true; 3028 label_color = c; 3029 radius = skip = 0.0; 3030} 3031 3032@ This routine is called |draw_|\dots\ due to its analogy to the other drawing 3033function. But curiously enough, I don't draw anything here, because labels are 3034drawn in |print_labels()| and |text|s consists only of labels. I only have to 3035assure that I don't include invisible labels. 3036 3037@c 3038void draw_text_labels(transformation& mytransform, texts_list& texts, 3039 objects_list& objects) { 3040 texts_list::iterator p = texts.begin(); 3041 while (p != texts.end()) { 3042 double x,y; 3043 if (mytransform.polar_projection(p->rectascension, 3044 p->declination, 3045 x,y)) { 3046 p->in_view = visible; 3047 p->x = x; 3048 p->y = y; 3049 objects.push_back(&(*p)); 3050 } 3051 p++; 3052 } 3053} 3054 3055@*1 Flex labels. ``Flex labels'' are drawn along a path that needn't (and 3056usually isn't) be a straight line. Unfortunately, due to this, their 3057dimensions are very difficult to predict and therefore they are {\it not\/} 3058part of the penalty algorithm -- at least not in the current version. 3059 3060Eventually \PPTHREE/ could have many kinds of flexes, therefore there is a 3061common (abstract) ancestor for all of them called |flex_label|. But at the 3062moment I only implement the by far most important one, the |declination_flex|, 3063see below. 3064 3065Flexes are special also in another respect: They are read from the input 3066script. This means that the |flexes_list| that contains all flexes (of all 3067kinds) is filled during reading the input script. Therefore there is no {\it 3068read\_flexes\/}(\,), and no file format associated with it. 3069 3070@s flex_label int 3071@s declination_flex int 3072 3073@q')}@> 3074 3075@c 3076struct flex_label : public view_data { 3077 double rectascension, declination; 3078 flex_label(string s, double r, double d, color c, int a, bool b); 3079 virtual bool draw(const transformation& mytransform, 3080 dimensions_list& dimensions, objects_list& objects) 3081 const = 0; 3082}; 3083 3084@ Of course I need no extra label because a flex is a label of its own. So a 3085flex only uses |label| and |label_angle|. The rest is unimportant because the 3086label isn't printed anyway because the coodrinates |x| and |y| are invalid 3087anyway and therefore printing will be rejected in |print_labels()|. 3088 3089Actually |on_baseline| is insignificant for flexes. 3090 3091@c 3092flex_label::flex_label(string s, double r, double d, color c, int a, bool b) 3093 : rectascension(r), declination(d) { 3094 label_color = c; 3095 label = string("\\FlexLabel{") + s + '}'; 3096 label_angle = a; 3097 on_baseline = b; 3098 in_view = visible; 3099 with_label = hidden; 3100 label_arranged = true; 3101} 3102 3103typedef list<flex_label*> flexes_list; 3104 3105@ |declination_flex| enables us to write a text along a path of constant 3106declination. This looks very nice on the chart. 3107 3108@c 3109struct declination_flex : public flex_label { 3110 declination_flex(string s, double r, double d, color c, int a, bool b) 3111 : flex_label(s,r,d,c,a,b) { } 3112 virtual bool draw(const transformation& mytransform, 3113 dimensions_list& dimensions, objects_list& objects) 3114 const; 3115 virtual double penalties_with(const double left, const double right, 3116 const double top, const double bottom, 3117 bool core = true) const; 3118}; 3119 3120@ This is the main declination flex routine. I calculate the dimensions of the 3121label in order to find out how long the path must be and how much it must be 3122shifted vertically in order to get the desired angular positioning. Both 3123values can only be estimates. (For example, the scale is not constant on the 3124map, so it cannot work perfectly. However I try to assure that the path will 3125be too long rather than too short.) 3126 3127FixMe: This routine should take distortions due to the used map projection into 3128account. 3129 3130Then I calculate the coordinates of three points that form the path. Start 3131point, end point, and one exactly in between. Of course all have the same 3132declination. |lower| is measured in em and denotes the amount by which the 3133whole text is shifted downwards. This is sub-optimal because it may affect 3134letterspacing disadvantageously. 3135 3136Finally the three coordinate pairs and the label text are sent to the output in 3137form of a \PS/Tricks text path command. 3138 3139@c 3140bool declination_flex::draw(const transformation& mytransform, 3141 dimensions_list& dimensions, 3142 objects_list& objects) const { 3143 if (dimensions.find(label) == dimensions.end()) 3144 recalculate_dimensions(dimensions, objects); 3145 if (dimensions.find(label) == dimensions.end()) 3146 throw string("Update of label dimensions file failed: \"") + 3147 label + "\" not found"; 3148 const double label_width = dimensions[label].width; 3149 const double label_height = dimensions[label].height; 3150 const double rectascension_halfwidth = label_width 3151 * mytransform.get_rad_per_cm() * 180.0/M_PI / 15.0 3152 / cos(declination * M_PI/180.0) / 2.0; 3153 char justification; 3154 double path_point_rectascension[3]; 3155 path_point_rectascension[0] = rectascension; 3156 path_point_rectascension[1] = rectascension - rectascension_halfwidth; 3157 path_point_rectascension[2] = rectascension - 3158 2.0 * rectascension_halfwidth; 3159 switch (label_angle) { 3160 case 0: case 1: case 7: 3161 justification = 'l'; 3162 break; 3163 case 2: case 6: 3164 justification = 'c'; 3165 for (int i = 0; i < 3; i++) 3166 path_point_rectascension[i] += rectascension_halfwidth; 3167 break; 3168 case 3: case 4: case 5: 3169 justification = 'r'; 3170 for (int i = 0; i < 3; i++) 3171 path_point_rectascension[i] += 2.0 * rectascension_halfwidth; 3172 break; 3173 } 3174 double lower; 3175 switch (label_angle) { 3176 case 0: case 4: lower = -0.4; break; 3177 case 1: case 2: case 3: lower = 0.0; break; 3178 case 5: case 6: case 7: lower = -0.8; break; 3179 } 3180 double x[3],y[3]; 3181 for (int i = 0; i < 3; i++) 3182 if (!mytransform.polar_projection(path_point_rectascension[i], 3183 declination, x[i],y[i])) 3184 return false; 3185 3186 label_color.set(OUT); 3187 OUT << "\\Label{\\pstextpath[" << justification 3188 << "](0," << lower << "em){\\pscurve[linestyle=none]%\n "; 3189 for (int i = 0; i < 3; i++) 3190 OUT << '(' << x[i] << "cm," << y[i] << "cm)"; 3191 OUT << "}{\\dummycolor" << label << "}}%\n"; 3192 3193 return true; 3194} 3195 3196@ As I've already said, a flex is (at the moment) excluded from the penalty 3197algorithm. This is realised here: This routine always returns zero. 3198 3199@c 3200double declination_flex::penalties_with(const double left, const double right, 3201 const double top, const double bottom, 3202 bool core) const { 3203 return 0.0; 3204} 3205 3206@ This is the drawing routine called from the main program. It goes through 3207all flexes twice: First it only fills |objects|. Then it actually draws the 3208flexes. The reason is efficiency: This assures that all needed labels are 3209available before the first flex is about to be draw which may mean a 3210reclaculation of the label dimensions. If I realised this in one pass, the 3211needed labels would occure over and over again, and every time it would be 3212necessary to recalculate the dimensions. 3213 3214@c 3215void draw_flex_labels(const transformation& mytransform, 3216 const flexes_list& flexes, objects_list& objects, 3217 dimensions_list& dimensions) { 3218 flexes_list::const_iterator p = flexes.begin(); 3219 while (p != flexes.end()) objects.push_back(*p++); 3220 p = flexes.begin(); 3221 while (p != flexes.end()) (*p++)->draw(mytransform,dimensions,objects); 3222} 3223 3224@* Drawing stars. Stars are a little bit simpler than nebulae because they are 3225mere disks. They are only included if they have a certain minimal magnitude. 3226The disk radius is calculated according to $$\eqalign{ \hbox{|radius|} &= 3227\sqrt{m_{\hbox{\sevenrm min}} - m + \hbox{\it radius}_{\hbox{\sevenrm 3228min}}^2}\,,\quad\hbox{if $m<m_{\hbox{\sevenrm min}}$\,,}\cr 3229\noalign{\vskip0.5ex} \hbox{|radius|} &= m_{\hbox{\sevenrm min}}\,,\quad 3230\hbox{otherwise}.}$$ 3231 3232The star gets a label by default only if it has a certain magnitude. This is 3233even a little bit stricter than the related condition above. 3234 3235Then only the stellar colour has yet to be calculated, and it can be printed. 3236 3237@q;@> 3238 3239@c 3240@<|create_hs_colour()| for star colour determination@>@;@# 3241 3242void draw_stars(const transformation& mytransform, stars_list& stars, 3243 objects_list& objects) { 3244 for (int i = 0; i < stars.size(); i++) 3245 if (stars[i].in_view != hidden && 3246 stars[i].magnitude < params.faintest_star_magnitude) { 3247 // Effectively all stars of the \BSC/ 3248 if (mytransform.polar_projection(stars[i].rectascension, 3249 stars[i].declination, 3250 stars[i].x,stars[i].y)) { 3251 stars[i].in_view = visible; 3252 const double m_dot = params.faintest_star_disk_magnitude; 3253 const double r_min = params.minimal_star_radius / 3254 params.star_scaling; 3255 stars[i].radius = params.star_scaling * 3256 (stars[i].magnitude < m_dot ? 3257 sqrt((m_dot - stars[i].magnitude)/300.0 + r_min*r_min) 3258 : r_min); 3259 if (stars[i].with_label == undecided) 3260 stars[i].with_label = 3261 (stars[i].magnitude < 3262 params.faintest_star_with_label_magnitude && 3263 !stars[i].name.empty()) ? visible : hidden; 3264 if (params.colored_stars) { 3265 OUT << "\\newhsbcolor{starcolor}{"; 3266 create_hs_colour(stars[i].b_v,stars[i].spectral_class); 3267 OUT << " 1}%\n"; 3268 } else OUT << params.starcolor; 3269 OUT << "\\pscircle*[linecolor=starcolor](" 3270 << stars[i].x << "," 3271 << stars[i].y << "){" 3272 << stars[i].radius / 2.54 * 72.27 << "pt}%\n"; 3273 objects.push_back(&stars[i]); 3274 } else stars[i].in_view = hidden; 3275 } else stars[i].in_view = stars[i].with_label = hidden; 3276} 3277 3278@ I want to use the B$-$V magnitude for the colour of the star disks on the 3279maps. Here I map the value of the B$-$V magnitude to a colour in the \HSB/ 3280space. `\HSB/' -- `Hue, Saturation, Brightness' (all three fom $0$ to~$1$). 3281Brightness is always~$1$, so only hue and saturation have to be calculated. 3282 3283There are three intervals for B$-$V with the boundaries |bv0|, |bv1|, |bv2|, 3284and |bv3|. |bv0|--|bv1| is blue, |bv1|--|bv2| is white, and |bv2|--|bv3| is 3285red. On each boundary, the hue values |hue0|--|hue3| respectively are valid. 3286Inbetween I interpolate linearly (rule of three). 3287 3288@q;@> 3289 3290@<|create_hs_colour()| for star colour determination@>= 3291void create_hs_colour(double b_v, string spectral_class) { 3292 double hue, saturation; 3293 const double bv0 = -0.1, bv1 = 0.001, bv2 = 0.62, bv3 = 1.7; 3294 const double hue0 = 0.6, hue1 = 0.47, hue2 = 0.17, hue3 = 0.0; 3295 const double min_saturation = 0.0, max_saturation = 0.2; 3296 @<Handle missing B$-$V value@>@; 3297 if (b_v < bv0) b_v = bv0; // cut off extreme values 3298 if (b_v > bv3) b_v = bv3; 3299 if (b_v < bv1) { // blue star 3300 hue = (b_v - bv0) / (bv1 - bv0) 3301 * (hue1 - hue0) + hue0; 3302 saturation = (b_v - bv0) / (bv1 - bv0) 3303 * (min_saturation - max_saturation) + max_saturation; 3304 } 3305 else if (b_v < bv2) { // white star: constantly white. 3306 hue = 0.3; // could be anything 3307 saturation = 0; 3308 } 3309 else { // red star 3310 hue = (b_v - bv2) / (bv3 - bv2) 3311 * (hue3 - hue2) + hue2; 3312 saturation = (b_v - bv2) / (bv3 - bv2) 3313 * (max_saturation - min_saturation) + min_saturation; 3314 } 3315 OUT << hue << ' ' << saturation; 3316} 3317 3318@ Since there are some stars in the stellar catalogue without a B$-$V 3319brightness, I need a fallback on the spectral class. For such stars is 3320$\hbox{|b_v|} = 99$. In this routine I use the very first character in the 3321string with the spectral class for determining an estimated value for 3322B$-$V\spacefactor1000. The values are averages of all stars in the \BSC/ with 3323the respective spectral class. 3324 3325@<Handle missing B$-$V value@>= 3326 if (b_v > 90.0) { 3327 switch (spectral_class[0]) { 3328 case 'O': b_v = 0.0; @+ break; 3329 case 'B': b_v = -0.07; @+ break; 3330 case 'A': b_v = 0.11; @+ break; 3331 case 'F': b_v = 0.43; @+ break; 3332 case 'G': b_v = 0.89; @+ break; 3333 case 'K': b_v = 1.24; @+ break; 3334 case 'M': b_v = 1.62; @+ break; 3335 case 'N': b_v = 2.88; @+ break; 3336 case 'S': b_v = 1.84; @+ break; 3337 case 'C': b_v = 3.02; @+ break; 3338 default: b_v = 0.0; @+ break; 3339 } 3340 } 3341 3342 3343@* Drawing nebulae. Only nebulae with a certain minimal brightness are 3344included, and all Messier objects, but all of these get a label by default. 3345 3346The first decision I have to make here is whether the nebula has all necessary 3347data for drawing a neat ellipsis that has the correct diameters and the correct 3348angle. If this is not anvailable (|horizontal_angle|${}=720^\circ$), the 3349nebula ellipsis is re-calculated so that $$\eqalign{ \hbox{|diameter_x|}_ 3350{\hbox{\sevenrm new}} &= \hbox{|diameter_y|}_ {\hbox{\sevenrm 3351new}}\quad\hbox{and}\cr \noalign{\vskip0.5ex} \hbox{|diameter_x|}_ 3352{\hbox{\sevenrm new}} \cdot \hbox{|diameter_y|}_ {\hbox{\sevenrm new}} &= 3353\hbox{|diameter_x|}_ {\hbox{\sevenrm old}} \cdot \hbox{|diameter_y|}_ 3354{\hbox{\sevenrm old}}}$$ (make the ellipsis a circle of the same area) which 3355means $$\hbox{|diameter_x|}_ {\hbox{\sevenrm new}} \mathrel{:=} 3356\hbox{|diameter_y|}_ {\hbox{\sevenrm new}} \mathrel{:=} 3357\sqrt{\hbox{|diameter_x|}_ {\hbox{\sevenrm old}} \cdot \hbox{|diameter_y|}_ 3358{\hbox{\sevenrm old}}}.$$ 3359 3360The |radius| of the nebula is a rough estimate: It is simply the half of 3361|diameter_x|. If the nebula is too small, it is printed as a minimal circle. 3362If it's large enough, it is printed in its (almost) full beauty, see |@<Draw 3363nebula shape@>|. 3364 3365@q'@> 3366 3367@c 3368void draw_nebulae(const transformation& mytransform, nebulae_list& nebulae, 3369 objects_list& objects) { 3370 OUT << "\\psset{linecolor=nebulacolor,linewidth=" 3371 << params.linewidth_nebula << "cm,linestyle=" 3372 << params.linestyle_nebula << ",curvature=1 .5 -1}%\n"; 3373 for (int i = 0; i < nebulae.size(); i++) 3374 if (nebulae[i].in_view == visible || 3375 (nebulae[i].in_view == undecided && 3376 (((nebulae[i].kind == open_cluster || 3377 nebulae[i].kind == globular_cluster) 3378 && nebulae[i].magnitude < params.faintest_cluster_magnitude) 3379 || @/ 3380 ((nebulae[i].kind == galaxy || nebulae[i].kind == reflection || 3381 nebulae[i].kind == emission) && 3382 nebulae[i].magnitude < params.faintest_diffuse_nebula_magnitude) 3383 || 3384 nebulae[i].messier > 0 ))) { 3385 if (mytransform.polar_projection(nebulae[i].rectascension, 3386 nebulae[i].declination, 3387 nebulae[i].x,nebulae[i].y)) { 3388 nebulae[i].in_view = visible; 3389 if (nebulae[i].horizontal_angle > 360.0) 3390 nebulae[i].diameter_x = nebulae[i].diameter_y = 3391 sqrt(nebulae[i].diameter_x * nebulae[i].diameter_y); 3392 nebulae[i].radius = nebulae[i].diameter_x/2.0 / 3393 mytransform.get_rad_per_cm() * M_PI / 180.0; 3394 if (nebulae[i].with_label != hidden) 3395 nebulae[i].with_label = visible; 3396 if (nebulae[i].radius > params.minimal_nebula_radius) { 3397 @<Draw nebula shape@>@; 3398 } else { 3399 nebulae[i].radius = params.minimal_nebula_radius; 3400 OUT << "\\pscircle(" 3401 << nebulae[i].x << "," 3402 << nebulae[i].y << "){" 3403 << nebulae[i].radius / 2.54 * 72.27 << "pt}%\n"; 3404 } 3405 objects.push_back(&nebulae[i]); 3406 } else nebulae[i].in_view = hidden; 3407 } else nebulae[i].in_view = nebulae[i].with_label = hidden; 3408} 3409 3410@ This is the core of |draw_nebula()|. In order to draw the (almost) ellipsis, 3411I define four reference points at the vertexes of the ellipsis. In the loop 3412they are then transformed to screen coordinates and printed. 3413 3414Mathematically, the algorithm used here works only for infitesimally small 3415nebulae on the equator. The problem of ``finding a point that is $x$ degrees 3416left from the current point with an angle of $\alpha$ degrees'' is actually 3417much more difficult. This is also the reason for this special case 3418|nebulae[i]|\hskip0pt|@[.diameter_x@] == nebulae[i]|\hskip0pt|@[.diameter_y@]|. 3419It shouldn't be necessary, and at the rim of the view frame it's even wrong due 3420to the different circular scale. FixMe: Improve this. (Via rotation matrices.) 3421 3422@q;@> 3423 3424@<Draw nebula shape@>= 3425 if (nebulae[i].diameter_x == nebulae[i].diameter_y) 3426 OUT << "\\pscircle(" << nebulae[i].x << ',' << nebulae[i].y 3427 << "){" << nebulae[i].radius << "}%\n"; 3428 else { 3429 double rectascension[4], declination[4]; 3430 const double r_scale = 1.0 / cos(nebulae[i].declination * M_PI/180.0); 3431 const double cos_angle 3432 = cos(nebulae[i].horizontal_angle * M_PI/180.0); 3433 const double sin_angle 3434 = sin(nebulae[i].horizontal_angle * M_PI/180.0); 3435 const double half_x = nebulae[i].diameter_x/2.0; 3436 const double half_y = nebulae[i].diameter_y/2.0; 3437 rectascension[0] = nebulae[i].rectascension - 3438 half_x * cos_angle / 15.0 * r_scale; 3439 declination[0] = nebulae[i].declination - 3440 half_x * sin_angle; 3441 rectascension[1] = nebulae[i].rectascension + 3442 half_y * sin_angle / 15.0 * r_scale; 3443 declination[1] = nebulae[i].declination - 3444 half_y * cos_angle; 3445 rectascension[2] = nebulae[i].rectascension + 3446 half_x * cos_angle / 15.0 * r_scale; 3447 declination[2] = nebulae[i].declination + 3448 half_x * sin_angle; 3449 rectascension[3] = nebulae[i].rectascension - 3450 half_y * sin_angle / 15.0 * r_scale; 3451 declination[3] = nebulae[i].declination + 3452 half_y * cos_angle; 3453 OUT << "\\psccurve"; 3454 for (int j = 0; j < 4; j++) { 3455 double x,y; 3456 mytransform.polar_projection(rectascension[j], 3457 declination[j], x, y); 3458 OUT << '(' << x << ',' << y << ')'; 3459 } 3460 } 3461 OUT << "\\relax\n"; 3462 3463 3464@* Grid and other curves. It's boring to have only stars on the map. I want 3465to have the usual coordinate grid with rectascension and declination lines, 3466plus the ecliptic and maybe the galactic equator, plus the constellation 3467borders and constellation lines. This is done here. 3468 3469@ The first routine is basic for all the following: It 3470helps to draw a smooth curve through the given points. Actually it's a mere 3471code fragment that is used later several times, therefore it's declared 3472|inline| and it has unfortunately many parameters. 3473 3474|rectascension| and |declination| are the celestial coordinates of the point. 3475|i| is the number of the scan point in the current curve. Later it'll be the 3476loop variable. |within_curve| is |true| if I'm actually drawing a curve, and 3477|false| if the current segment is not visible. |steps| is the number of |i|'s 3478that I skip over before I deploy a curve point. (I {\it scan\/} much more 3479points than I actually {\it draw}, because the resulting curve is smoothed 3480anyway so a very high point density is not necessary.) 3481 3482|last_x| and |last_y| simply contain the values of |x| and |y| from the last 3483call, because I need them when I step outwards of the view frame: Then I must 3484draw a curve ending point at the {\it last\/} coordinates rather than the 3485current ones. 3486 3487|steps|${}=1$ denotes the ending point of a curve. 3488 3489FixMe: There is a very basic flaw here, namely that the curve directions in the 3490endpoints may be rather suboptimal. This is particularly annoying when drawing 3491a circle-like shape. However, a solution is not easy; so far the only help is 3492to make the points denser. 3493 3494@c 3495inline void add_curve_point(const double rectascension, 3496 const double declination, 3497 const transformation& transform, 3498 const int i, bool& within_curve, 3499 const int steps) { 3500 static double last_x, last_y; 3501 double x,y; 3502 if (transform.polar_projection(rectascension, declination, x, y)) { 3503 if (!within_curve) { // start a new one 3504 OUT << "\\pscurve" << "(" << x << ',' << y << ")"; 3505 within_curve = true; 3506 } else if (i % steps == 0) OUT << "(" << x << ',' << y << ")"; 3507 if (i % (steps*4) == 0 || steps == 1) OUT << "%\n"; 3508 /* line break every four coordinates */ 3509 } else 3510 if (within_curve) { // end the current curve 3511 OUT << "(" << last_x << ',' << last_y << ")" << "\\relax\n"; 3512 within_curve = false; 3513 } 3514 last_x = x; last_y = y; 3515} 3516 3517@ This is the principal grid routine that is called from the |main()| function. 3518|scans_per_cm| is the density of tests whether we are within the view frame or 3519not. By default, we scan once every millimetre. |point_distance| is given in 3520degrees and it's the distance between two actually drawn curve points. 3521 3522|scans_per_fullcircle| is the number of scan points in one full arc. It is 3523used in the following code for determining the number of loop repetitions. In 3524order to keep the meaning of all quantities, the grid creating code should 3525respect this variable. E.\,g., most declination circles are smaller than one 3526full arc. Therefore they mustn't use the full value of 3527|scans_per_fullcircle|. 3528 3529|steps| and |within_curve| should be clear from the routine 3530 |add_curve_point()|. |steps| is at least $2$, because it must be at least $1$ 3531 anyway and ``$1$'' has a special meaning in |add_curve_point|: It denotes the 3532 end of a curve. |within_curve| must be reset to |false| if a new set of lines 3533 is to be drawn. 3534 3535For a plot with closed lines (e.\,g.\ of one of the poles), you may set 3536|point_distance| to |0| and |scans_per_cm| to a higher value, for avoiding a 3537kink at the joint. 3538 3539@c 3540void create_grid(const transformation transform, 3541 const double scans_per_cm = 10, 3542 const double point_distance = 5.0) { 3543 if (!params.show_grid && !params.show_ecliptic) return; 3544 const double scans_per_fullcircle = 3545 scans_per_cm / transform.get_rad_per_cm() * 2.0*M_PI; 3546 const int steps = int((point_distance * M_PI/180.0) * 3547 (scans_per_fullcircle/(2.0*M_PI))) + 2; 3548 bool within_curve; 3549 if (params.show_grid) { 3550 OUT << "\\psset{linestyle=" << params.linestyle_grid 3551 << ",linecolor=gridcolor,linewidth=" 3552 << params.linewidth_grid << "cm}%\n"; 3553 @<Create grid lines for equal declination@>@; 3554 @<Create grid lines for equal rectascension@>@; 3555 } 3556 if (params.show_ecliptic) { 3557 @<Draw the ecliptic@>@; 3558 } 3559} 3560 3561@ As mentioned before, declination circles are smaller than the full circle of 3562the celestial sphere. Therefore I reduce the |scans_per_fullcircle| by 3563$cos(\hbox{|declination|})$ in order to decrese the number of scan points. The 3564equator is drawn with a slightly bigger line width. 3565 3566This strange construction with ``|i==number_of_points?1:steps|'' is necessary 3567because the very last point {\it must\/} be drawn. 3568 3569@<Create grid lines for equal declination@>= 3570 for (int declination = -80; declination <= 80; declination += 10) { 3571 if (declination == 0) 3572 OUT << "\\psset{linewidth=" << 2.0 * params.linewidth_grid 3573 << "cm}%\n"; 3574 within_curve = false; 3575 const int number_of_points = 3576 int(cos(declination*M_PI/180.0)*scans_per_fullcircle); 3577 for (int i = 0; i <= number_of_points; i++) 3578 add_curve_point(double(i)/double(number_of_points)*24.0, 3579 declination,transform,i,within_curve, 3580 i==number_of_points?1:steps); 3581 if (declination == 0) 3582 OUT << "\\psset{linewidth=" << params.linewidth_grid << "cm}%\n"; 3583 } 3584 3585@ The only slightly interesting thing here is that I draw the lines of equal 3586rectascension only from $-80^\circ$ to $+80^\circ$ of declination, because 3587otherwise it gets too populated in the pole regions. 3588 3589@<Create grid lines for equal rectascension@>= 3590 const int number_of_points = int(scans_per_fullcircle / 2.0); 3591 for (int rectascension = 0; rectascension <= 23; rectascension++) { 3592 within_curve = false; 3593 for (int i = 0; i <= number_of_points; i++) 3594 add_curve_point(double(rectascension), 3595 double(i)/double(number_of_points)*160.0 - 80.0, 3596 transform,i,within_curve, 3597 i==number_of_points?1:steps); 3598 } 3599 3600@ Unfortunately the naive approach for drawing the ecliptic, 3601$$\hbox{|declination|} = \varepsilon \cdot \sin(\hbox{|rectascension|})$$ (with 3602$\varepsilon$ being the angle between equator and ecliptic) is wrong. So I 3603have to take the equatorial declination circle, transform it into cartesian 3604coordinates, apply a simple rotation matrix for turning it into the ecliptical 3605circle, and transform it back into celestial coordinates. Fortunately the 3606ecliptic lies very symmetrically, so the resulting formulae are rather simple: 3607 3608Being $\varphi_0$ the right ascension angle ($=\hbox{\it rectascension} \cdot 360915^\circ\!/\hbox{h}$) of the point on the equator, its right ascension 3610$\varphi$ and declination $\delta$ after becoming the ecliptic are 3611$$\eqalign{\varphi &= \arctan_2\frac{-\sin\varphi_0 3612\cos\varepsilon}{\cos\varphi_0}\cr\delta &= \arcsin(-\sin\varphi_0 3613\sin\varepsilon).}$$ $\arctan_2$ is the quadrant-aware version of $\arctan$, 3614because $\arctan$ only returns values between $-\pi/2$ and $+\pi/2$. 3615 3616 3617@<Draw the ecliptic@>= 3618 OUT << "\\psset{linestyle=" << params.linestyle_ecliptic 3619 << ",linecolor=eclipticcolor," 3620 << "linewidth=" << params.linewidth_ecliptic << "cm}%\n"; 3621 { 3622 const double epsilon = 23.44 * M_PI / 180.0; 3623 const int number_of_points = int(scans_per_fullcircle); 3624 within_curve = false; 3625 for (int i = 0; i <= number_of_points; i++) { 3626 const double phi0 = double(i)/double(number_of_points)*2.0*M_PI; 3627 const double m_sin_phi0 = -sin(phi0); 3628 const double phi = atan2(m_sin_phi0 * cos(epsilon),cos(phi0)); 3629 const double delta = asin(m_sin_phi0*sin(epsilon)); 3630 add_curve_point(phi * 12.0 / M_PI, delta * 180.0 / M_PI, 3631 transform,i,within_curve, 3632 i==number_of_points?1:steps); 3633 } 3634 } 3635 3636@*1 Constellation boundaries. They are drawn at a quite early stage so that 3637they are overprinted by more interesting stuff. 3638 3639@ This routine is a helper and is used in |draw_constellation_boundaries()|. 3640It draws one boundary line~|b|, but only the part that is visible. For this in 3641the first part of this routine, the container |current_line| is filled with all 3642points of |b| that are actually visible, with their screen coordinates in 3643centimetres. 3644 3645If |current_line| consists only of a start and an end point, a `\.{\\psline}' 3646is created which is a straight line. I cannot get a curvature from anywhere 3647easily in this case. Otherwise it is a `\.{\\pscurve}' which means that we go 3648smoothly through all points. 3649 3650I need this |"[liftpen=2]"| because eventually the \.{\\pscurve} command may be 3651part of a \.{\\pscustom} command and thus be part of a bigger path that forms 3652-- in terms of the Porstscript language -- one united path.\footnote{$^2$}{This 3653means e.\,g.\ that a dashed line pattern won't @q'@> be broken at subpath 3654junctions.} In order to get crisp coners, the \.{liftpen} option is necessary. 3655 3656@c 3657void draw_boundary_line(const boundary& b, const transformation& transform, 3658 objects_list& objects, bool highlighted = false) { 3659 vector<point> current_line; 3660 for (int j = 0; j < b.points.size(); j++) { 3661 const double rectascension = b.points[j].x; 3662 const double declination = b.points[j].y; 3663 double x,y; 3664 if (!transform.polar_projection(rectascension,declination,x,y)) 3665 continue; 3666 current_line.push_back(point(x,y)); 3667 } 3668 if (current_line.size() >= 2) { 3669 if (current_line.size() == 2) OUT << "\\psline"; 3670 else OUT << "\\pscurve"; 3671 OUT << "[liftpen=2]{c-c}"; 3672 for (int j = 0; j < current_line.size(); j++) { 3673 OUT << '(' << current_line[j].x << ',' 3674 << current_line[j].y << ')'; 3675 if (j % 4 == 3) OUT << "%\n"; 3676 if (highlighted) 3677 @<Create a |boundary_atom| for the |objects|@>@; 3678 } 3679 OUT << "\\relax\n"; 3680 } 3681} 3682 3683@ This is the point where the |boundary_atom|s are actually created. As one 3684can see, it's very simple. I just draw a virtual line from the current point 3685in the current path to the previous one. Unfortunately I create new objects 3686here on the heap that are never deleted. But it's harmless nevertheless. 3687 3688@<Create a |boundary_atom| for the |objects|@>= 3689if (j > 0) objects.push_back(new boundary_atom(current_line[j], 3690 current_line[j-1])); 3691 3692@ This is the routine that is actually called from the main program. There are 3693two possibilities: Either the string |constellation| is empty or not. If not, 3694the caller wants to highlight the given constellation. Then we have to 3695undertake a two step process: \medskip 3696 3697\item{(1)} Draw all boundaries that do not belong to |constellation|. 3698\item{(2)} Draw all boundaries that enclose |constellation| in a hightlighted 3699style and {\it as one path\/} via \.{\\pscustom}. 3700 3701\medskip If no |constellation| is given we simply draw all lines in 3702|boundaries| in modus~(1). 3703 3704@c 3705void draw_boundaries(const transformation& mytransform, 3706 boundaries_list& boundaries, 3707 objects_list& objects, 3708 string constellation = string("")) { 3709 OUT << "\\psset{linecolor=boundarycolor,linewidth=" 3710 << params.linewidth_boundary << "cm," 3711 << "linestyle=" << params.linestyle_boundary << "}%\n"; 3712 if (!constellation.empty()) { 3713 for (int i = 0; i < boundaries.size(); i++) 3714 if (!boundaries[i].belongs_to_constellation(constellation)) 3715 draw_boundary_line(boundaries[i], mytransform, objects); 3716 OUT << "\\psset{linecolor=hlboundarycolor,linewidth=" 3717 << params.linewidth_boundary << "cm," << "linestyle=" 3718 << params.linestyle_hlboundary << "}%\n"; 3719 OUT << "\\pscustom{"; 3720 for (int i = 0; i < boundaries.size(); i++) 3721 if (boundaries[i].belongs_to_constellation(constellation)) 3722 draw_boundary_line(boundaries[i], mytransform, objects, true); 3723 OUT << "}%\n"; 3724 } else 3725 for (int i = 0; i < boundaries.size(); i++) 3726 draw_boundary_line(boundaries[i], mytransform, objects); 3727} 3728 3729@*1 Constellation lines. In the loop I test all lines available in 3730|connections|. The first thing in the loop is to assure that at least one of 3731stars is actually visible. At the beginning $(\hbox{|x1|}, \hbox{|y1|})$ and 3732$(\hbox{|x2|}, \hbox{|y2|})$ are the screen coordinates of the two stars that 3733are supposed to be connected by a line. Then I move from there to the 3734respective other star by the amount of |skip|. The current length of the 3735connection line is stored in~|r| which must have a minimal value (in particular 3736it must be positive), otherwise it doesn't make sense to draw the line. 3737Finally the line is drawn from the new $(\hbox{|x1|}, \hbox{|y1|})$ to the new 3738$(\hbox{|x2|}, \hbox{|y2|})$. 3739 3740I expect |has_valid_coordinates()| to be |false| if the star is on the 3741non-visible hemisphere (i.\,e.\ $z<0$). 3742 3743 3744@q'@> 3745 3746@c 3747void draw_constellation_lines(connections_list& connections, 3748 const stars_list& stars, 3749 objects_list& objects) { 3750 OUT << "\\psset{linecolor=clinecolor,linestyle=" << params.linestyle_cline 3751 << ",linewidth=" << params.linewidth_cline << "cm}%\n"; 3752 for (int i = 0; i < connections.size(); i++) 3753 if ((stars[connections[i].from].in_view == visible || 3754 stars[connections[i].to].in_view == visible) 3755 && stars[connections[i].from].has_valid_coordinates() 3756 && stars[connections[i].to].has_valid_coordinates()) { 3757 double x1 = stars[connections[i].from].x; 3758 double y1 = stars[connections[i].from].y; 3759 double x2 = stars[connections[i].to].x; 3760 double y2 = stars[connections[i].to].y; 3761 const double phi = atan2(y2 - y1, x2 - x1); 3762 double r = hypot(x2 - x1, y2 - y1); 3763 double skip; 3764 skip = stars[connections[i].from].radius + 3765 stars[connections[i].from].skip; 3766 r -= skip; 3767 x1 += skip * cos(phi); 3768 y1 += skip * sin(phi); 3769 skip = stars[connections[i].to].radius + 3770 stars[connections[i].to].skip; 3771 r -= skip; 3772 x2 -= skip * cos(phi); 3773 y2 -= skip * sin(phi); 3774 connections[i].radius = r/2.0; 3775 connections[i].x = (x1 + x2) / 2.0; 3776 connections[i].y = (y1 + y2) / 2.0; 3777 if (r > params.shortest_constellation_line && r > 0.0) { 3778 OUT << "\\psline{cc-cc}(" << x1 << ',' << y1 3779 << ")(" << x2 << ',' << y2 << ")%\n"; 3780 connections[i].start = point(x1,y1); 3781 connections[i].end = point(x2,y2); 3782 objects.push_back(&connections[i]); 3783 } 3784 } 3785 3786} 3787 3788 3789@* Drawing the Milky Way. If a proper data file is available, the milky way is 3790a simple concept, however difficult to digest for \LaTeX\ due to many many 3791Postscipt objects. But for this program it's so simple that I can do the 3792reading and drawing in one small routine and I even don't need any large data 3793structures. 3794 3795FixMe: Actually, under very adverse circumstances, it is possible that gaps 3796occur between the pixels. This is when polar regions (that fully use the 3797maximal pixel distance with \PPTHREE/'s original Milky Way data) lie on the rim 3798of a full hemisphere plot. This doesn't do much harm though. 3799 3800FixMe: Additionally, this routine should take into account a)~that the original 3801Milky Way data was in azimuthal equidistant projection and b)~the current map 3802projection. Both should result in optimised radii which would solve the above 3803problem too. 3804 3805See |@<Reading the milkyway into |pixels|@>| for more information on |pixels|. 3806 3807@c 3808void draw_milky_way(const transformation& mytransform) { 3809 vector<vector<point> > pixels(256); 3810 @<Reading the milkyway into |pixels|@>@; 3811 for (int i = 1; i < pixels.size(); i++) { 3812 if (pixels[i].size() == 0) continue; 3813 interpolate_colors(double(i) / 255.0, params.bgcolor, 3814 params.milkywaycolor).set(OUT); 3815 for (int j = 0; j < pixels[i].size(); j++) 3816 OUT << "\\qdisk(" << pixels[i][j].x << "," << pixels[i][j].y 3817 << "){" << radius << "pt}%\n"; 3818 } 3819} 3820 3821 3822@** The main function. This consists of six parts: \medskip 3823 3824\item{(1)} Command line interpretation. 3825\item{(2)} Definition of the desired transformation, here called |mytransform|. 3826\item{(3)} Definition of the containers and 3827\item{(4)} the filling of them by reading from text files. 3828\item{(5)} Creating of the \LaTeX\ file, first and foremost by calling the 3829drawing routines. 3830\item{(6)} Possibly create an \EPS/ or \PDF/ file by calling the necessary 3831programs. 3832 3833\medskip\noindent That's it. 3834 3835@q'@> 3836 3837@c 3838@<Routines for reading the input script@>@#@; 3839 3840int main(int argc, char **argv) { 3841 try { 3842 @<Dealing with command line arguments@>@; 3843 read_parameters_from_script(*params.in); 3844 if (!params.filename_output.empty()) 3845 params.out = new ofstream(params.filename_output.c_str()); 3846 transformation mytransform(params.center_rectascension, 3847 params.center_declination, 3848 params.view_frame_width, 3849 params.view_frame_height, 3850 params.grad_per_cm); 3851 3852 @<Definition and filling of the containers@>@; 3853 3854 read_objects_and_labels(*params.in, dimensions, objects, stars, 3855 nebulae, texts, flexes, mytransform); 3856 3857 OUT.setf(ios::fixed); // otherwise \LaTeX\ gets confused 3858 OUT.precision(3); 3859 @<Create \LaTeX\ header@>@; 3860 OUT << "\\psclip{\\psframe[linestyle=none](0bp,0bp)(" 3861 << params.view_frame_width_in_bp() 3862 << "bp," << params.view_frame_height_in_bp() << "bp)}%\n" 3863 << "\\psframe*[linecolor=bgcolor,linestyle=none](-1bp,-1bp)(" 3864 << params.view_frame_width_in_bp() + 1 3865 << "bp," << params.view_frame_height_in_bp() + 1 << "bp)%\n"; 3866 @<Draw all celestial objects and labels@>@; 3867 OUT << "\\endpsclip\n"; 3868 @<Create \LaTeX\ footer@>@; 3869 if (params.input_file) delete params.in; 3870 if (!params.filename_output.empty()) 3871 delete params.out; else OUT.flush(); 3872 @<Create \EPS/ or \PDF/ file if requested@>@; 3873 } 3874 catch (string s) { 3875 cerr << "pp3: " << s << '.' << endl; 3876 exit(1); 3877 } 3878 return 0; 3879} 3880 3881@ \PPTHREE/ needs exactly one argument. It must be either the file name of the 3882input script or a `\.{-}' which means that it takes standard input for reading 3883the input script. 3884 3885@q'@> 3886 3887@<Dealing with command line arguments@>= 3888 if (argc == 2) { 3889 if (argv[1][0] != '-') { 3890 params.in = new ifstream(argv[1]); 3891 if (!params.in->good()) 3892 throw string("Input script file ") + argv[1] 3893 + " not found"; 3894 else params.input_file = true; 3895 } else if (!strcmp(argv[1],"-")) params.in = &cin; else 3896 cerr << "Invalid argument: " << argv[1] << endl; 3897 } 3898 if (params.in == 0) { 3899 cerr << "PP3 1.3.2 Copyright (c) 2002--2004 Torsten Bronger\n" @/ 3900 << " http://pp3.sourceforge.net\n\n" @/ 3901 << "Syntax:\n pp3 {input-file}\n\n" @/ 3902 << "{input-file} may be \"-\" to denote standard input.\n" @/ 3903 << "You may give an empty file to get a default plot.\n" @/ 3904 << "The plot is sent to standard output by default.\n"; 3905 exit(0); 3906 } 3907 3908@ I must define all containers, but of course I only read those data structures 3909that are actually used. 3910 3911@<Definition and filling of the containers@>= 3912 boundaries_list boundaries; 3913 dimensions_list dimensions; 3914 objects_list objects; 3915 stars_list stars; 3916 nebulae_list nebulae; 3917 connections_list connections; 3918 texts_list texts; 3919 flexes_list flexes; 3920 3921 read_stars(stars); /* Must come first, because it tests whether 3922 data files can be found */ 3923 if (params.show_boundaries) read_constellation_boundaries(boundaries); 3924 read_label_dimensions(dimensions); 3925 if (params.nebulae_visible) read_nebulae(nebulae); 3926 if (params.show_lines) read_constellation_lines(connections, stars); 3927 3928@ Four calls here are not preceded by an |if| clause: |create_grid()| contains 3929such tests of its own (because it's divided into subsections), and in 3930|draw_flex_labels()|, |draw_text_labels()|, and |draw_stars()| every single 3931object is tested for visibility anyway. 3932 3933@q')@> 3934 3935@<Draw all celestial objects and labels@>= 3936 if (params.milkyway_visible) draw_milky_way(mytransform); 3937 create_grid(mytransform); 3938 if (params.show_boundaries) 3939 draw_boundaries(mytransform, boundaries, objects, 3940 params.constellation); 3941 draw_flex_labels(mytransform, flexes, objects, dimensions); 3942 draw_text_labels(mytransform, texts, objects); 3943 if (params.nebulae_visible) draw_nebulae(mytransform, nebulae, objects); 3944 draw_stars(mytransform, stars, objects); 3945 if (params.show_lines) 3946 draw_constellation_lines(connections, stars, objects); 3947 if (params.show_labels) { 3948 arrange_labels(objects,dimensions); 3949 print_labels(objects); 3950 } 3951 3952@ This is the preamble and the beginning of the resulting \LaTeX\ file. I use 3953the \.{geometry} package and a dvips \.{\\special} command to set the papersize 3954to the actual view frame plus 2~millimetres. So I create a buffer border of 39551\,mm thickness. 3956 3957@<Create \LaTeX\ header@>= 3958 create_preamble(OUT); 3959 OUT << "\\usepackage[nohead,nofoot,margin=0cm," @/ 3960 << "paperwidth=" << params.view_frame_width_in_bp() << "bp," @/ 3961 << "paperheight=" << params.view_frame_height_in_bp() << "bp" @/ 3962 << "]{geometry}\n\n" @/ 3963 << "\n\\begin{document}\\parindent0pt\n" @/ 3964 << "\\pagestyle{empty}\\thispagestyle{empty}%\n" @/ 3965 << "\\special{papersize=" << params.view_frame_width_in_bp() - 0.1 3966 << "bp," << params.view_frame_height_in_bp() - 0.1 << "bp}%\n" @/ 3967 << "\\vbox to " << params.view_frame_height_in_bp() << "bp{" @/ 3968 << "\\vfill" @/ 3969 << "\\hbox to " << params.view_frame_width_in_bp() << "bp{%\n" @/ 3970 << params.bgcolor << params.gridcolor << params.eclipticcolor 3971 << params.boundarycolor << params.hlboundarycolor << params.starcolor 3972 << params.nebulacolor << params.clinecolor; 3973 3974@ If no preamble filename was given in the input script, a default preamble is 3975used. If a preamble filename was given, the file should contain only font 3976specifying commands. A possible preamble may be: \medskip 3977 3978{\parindent2em\baselineskip10.5pt\ninett\obeylines 3979\BS usepackage\LB eulervm\RB 3980\BS usepackage[T1]\LB fontenc\RB 3981\BS renewcommand*\LB \BS rmdefault\RB \LB pmy\RB \par} 3982 3983\medskip\noindent You may also have use for a command like \.{\BS 3984AtBeginDocument\LB\BS sffamily\RB} or \.{\BS boldmath} or \.{\BS large} or 3985whatever. Or let be inspired by this fragment: \medskip 3986 3987{\parindent2em\baselineskip10.5pt\ninett 3988\BS usepackage\LB relsize\RB\par 3989\BS renewcommand*\LB\BS NGC\RB[1]\LB\BS smaller \#1\RB\par 3990\BS renewcommand*\LB\BS IC\RB[1]\LB\BS smaller\LB\BS smaller 3991 IC\BS,\RB\#1\RB\par 3992\BS renewcommand*\LB\BS Messier\RB[1]\LB\BS smaller\LB\BS smaller 3993 M\BS,\RB\#1\RB\par} 3994 3995\medskip The default preamble activates Adobe Symbol Greek letters, and 3996Helvetica Roman letters. If you define a preamble of your own, you don't have 3997to re-define that, because it's not defined then. You can assume a naked plain 3998\LaTeX\ font setup. 3999 4000@^hooks in preamble@> 4001@:Label}{\.{\BS Label}@> 4002@:NGC}{\.{\BS NGC}@> 4003@:IC}{\.{\BS IC}@> 4004@:Messier}{\.{\BS Messier}@> 4005@:TextLabel}{\.{\BS TextLabel}@> 4006@:Starname}{\.{\BS Starname}@> 4007@:FlexLabel}{\.{\BS FlexLabel}@> 4008@:TicMark}{\.{\BS TicMark}@> 4009@:DP}{\.{\BS DP}@> 4010@^decimal point@> 4011 4012I define a couple of \LaTeX\ commands as hooks here, all of them take exactly 4013one argument. ``\.{\BS Label}'' encloses every label. In addition to that, 4014``\.{\BS NGC}'', ``\.{\BS IC}'', and ``\.{\BS Messier}'' are built around the 4015nebula label of the respective type, ``\.{\BS Starname}'' around all star 4016labels, ``\.{\BS TextLabel}'' around text labels, and ``\.{\BS FlexLabel}'' 4017around flex labels. ``\.{\BS TicMark}'' encloses all coordinate labels 4018generated with the \.{tics} option. By default, they do nothing. Well, 4019nothing to speak of. 4020 4021@^preamble (\LaTeX)@> 4022 4023@<|create_preamble()| for writing the \LaTeX\ preamble@>= 4024void create_preamble(ostream& out) { 4025 out << "\\documentclass[" << params.font_size << "pt]{article}\n\n" @/ 4026 << "\\nofiles" @/ 4027 << "\\usepackage[dvips]{color}\n" @/ 4028 << "\\usepackage{pstricks,pst-text}\n" @/ 4029 << "\\newcommand*{\\DP}{.}\n" @/ 4030 << "\\newcommand*{\\TicMark}[1]{#1}\n" @/ 4031 << "\\newcommand*{\\Label}[1]{#1}\n" @/ 4032 << "\\newcommand*{\\TextLabel}[1]{#1}\n" @/ 4033 << "\\newcommand*{\\FlexLabel}[1]{#1}\n" @/ 4034 << "\\newcommand*{\\Starname}[1]{#1}\n" @/ 4035 << "\\newcommand*{\\Messier}[1]{M\\,#1}\n" @/ 4036 << "\\newcommand*{\\NGC}[1]{NGC\\,#1}\n" @/ 4037 << "\\newcommand*{\\IC}[1]{IC\\,#1}\n\n"; 4038 4039 if (params.filename_preamble.empty()) @/ 4040 out << "\\usepackage{mathptmx}\n" @/ 4041 << "\\usepackage{helvet}\n" @/ 4042 << "\\AtBeginDocument{\\sffamily}\n"; 4043 else out << "\\input " << params.filename_preamble << '\n'; 4044} 4045 4046@ This is almost trivial. I just close the box structure I began at the end of 4047|@<Create \LaTeX\ header@>| and close the document. 4048 4049@<Create \LaTeX\ footer@>= 4050 OUT << "\\hfill}}\\end{document}\n"; 4051 4052@ Here I call \LaTeX, dvips, and/or \PS/2\PDF/ in order to create the 4053output the user wanted to have eventually in the input script. 4054 4055@<Create \EPS/ or \PDF/ file if requested@>= 4056 if (!params.filename_output.empty() && (params.create_eps 4057 || params.create_pdf)) { 4058 string commandline = string("latex ") + params.filename_output; 4059 if (system(commandline.c_str()) == 0) { 4060 string base_filename(params.filename_output); 4061 if (base_filename.find('.') != string::npos) 4062 base_filename.erase(base_filename.find('.')); 4063 commandline = string("dvips -o ") + base_filename + ".eps " 4064 + base_filename; 4065 if (system(commandline.c_str()) == 0) { 4066 if (params.create_pdf) { 4067 commandline = string("ps2pdf ") + 4068 "-dCompatibility=1.3 " + 4069 base_filename + ".eps " + base_filename + ".pdf"; 4070 if (system(commandline.c_str()) != 0) 4071 throw string("ps2pdf call failed: ") + commandline; 4072 } 4073 } else throw string("dvips call failed: ") + commandline; 4074 } else throw string("LaTeX call failed: ") + commandline; 4075 } 4076 4077 4078@** Index. 4079