Schyren-Gymnasium

Pfaffenhofen a.d. Ilm

 

 

Kollegstufe

Abiturjahrgang 2000

Facharbeit

Physik

Der Swing-by-Effekt, erklärt
anhand der Voyager-Mission.
Computerprogramm.

 

Verfasser:                        Martin Franz Stephan Freiherr von Gagern

Leistungskurs:                 Ph11 bzw. 13Ph

Kursleiter:                       Reinhard Breitsameter

Bearbeitungszeitraum:     11. Februar 1999 bis 1. Februar 2000

Abgabetermin:                 1. Februar 2000, 1000h

 

Erzielte Note:

 

(mit Tendenz)

 

Erzielte Punkte:

 

(einfache Wertung)

 

 

(Reinhard Breitsameter)

 × 
Dies ist eine aus Word exportierte HTML-Datei.
Die PDF-Version ist womöglich besser geeignet.

 


Inhalt

1          Einleitung

2          Das Gravitationsgesetz und seine Folgen

2.1   Die Grundlage: Newtons Gravitationsgesetz

2.2   Drehimpuls

2.3   Flächensatz

2.4   Ebene Bewegung

2.5   Energie und Potential

2.6   Herleitung der Bahnfiguren

2.6.1   Umformungen

2.6.2   Substitution

2.6.3   Alternative Darstellung der Kraft

2.6.4   Das Lösen der Differentialgleichung

2.6.5   Deutung als Kegelschnittfigur

2.7   Energie und Bahnfigur

2.8   Geschwindigkeit

3          Theorie des Swing-by

3.1   Qualitative Beschreibung des Vorgangs

3.2   Annahmen und Näherungen

3.3   Berechnung des Energiegewinns

3.4   Voyager I als Beispiel

3.4.1   Allgemeines

3.4.2   Der Start

3.4.3   Der Flug von der Erde zum Jupiter

3.4.4   Die Begegnung mit Jupiter

3.4.5   Der Direktflug zum Vergleich

4          Problemlösungsansätze für das Programm

4.1   Koordinatensystem und Bahnelemente

4.1.1   Kartesische Koordinaten

4.1.2   Längen- und Breitengrade

4.1.3   Bahnelemente mit Längengraden

4.1.4   Ekliptikale Bahnelemente

4.1.5   Anwendung ekliptikaler Eelemente

4.1.6   Umwandlung des Längengradformates

4.2   Das Format der Daten

4.2.1   Planetendaten

4.2.2   Voyager Bahndaten

4.3   Prinzip der Interpolation

4.4   Flächenberechnungen

4.4.1   Affine Abbildungen

4.4.2   Elliptische Bahnen

4.4.3   Hyperbolische Bahnen

4.5   Die schrittweise Simulation

4.6   Die Gliederung in Phasen

4.7   Die Darstellung

4.8   Die Datenbank

5          Programmierung mit C

5.1   Warum C

5.2   Grundsätzliches zu C und C

5.3   Objektorientierte Programmierung

5.3.1   Grundlagen der objektorientierten Programmierung

5.3.2   Vererbung

5.3.3   Konstruktor

5.3.4   Statische Elemente

5.3.5   Kettenstrukturen

6          Das Programm

6.1   Übersicht über die Hierarchie der Klassen

6.2   Allgemeine Definitionen

6.3   Die Klasse CVector

6.3.1   Allgemeines über überladene Operatoren

6.3.2   Quellcode

6.3.2.1   Vector.h

6.3.2.2   Vector.cc

6.4   Die Klasse CDataElement und davon abgeleitete

6.4.1   Zugriff auf die Datenelemente der Datenbankblöcke

6.4.2   Quellcode

6.4.2.1   DataElement.h

6.4.2.2   DataElement.cc

6.5   Die Klasse CData

6.5.1   Minimalanforderung für jeden Block

6.5.2   CData und die Datenbank

6.5.3   Quellcode

6.5.3.1   Data.h

6.5.3.2   Data.cc

6.6   Die Klasse CPoint

6.6.1   Einstellungen für die Darstellung

6.6.2   Darstellung mit der Klasse CPoint

6.6.3   Quellcode

6.6.3.1   Point.h

6.6.3.2   Point.cc

6.7   Die Klasse CMass

6.7.1   Der Block [Sun]

6.7.2   Die Berechnung der Beschleunigung

6.7.3   Quellcode

6.7.3.1   Mass.h

6.7.3.2   Mass.cc

6.8   Die Klasse CPlanet

6.8.1   Der Block [Planet]

6.8.2   Die Berechnung der Skelettdaten

6.8.3   Quellcode

6.8.3.1   Planet.h

6.8.3.2   Planet.cc

6.9   Die Klasse CPath

6.9.1   Der Block [Path]

6.9.2   CPath im Unterschied zu CPlanet

6.9.3   Die Geschwindigkeit im Perihelpunkt

6.9.4   Quellcode

6.9.4.1   Path.h

6.9.4.2   Path.cc

6.10 Die Klasse CInit

6.10.1 Der Block [Init]

6.10.2 Start der Simulation

6.10.3 Möglichkeiten zur Angabe der Startwerte

6.10.4 Quellcode

6.10.4.1 Init.h

6.10.4.2 Init.cc

6.11 Die Klasse CPhase

6.11.1 Der Block [Phase]

6.11.2 Die Darstellungsperspektive

6.11.3 Das Ende einer Flugphase

6.11.4 Quellcode

6.11.4.1 Phase.h

6.11.4.2 Phase.cc

6.12 Das Hauptprogramm

6.12.1 Start des Programms

6.12.2 Koordination der Blöcke

6.12.3 voyager.cc

6.13 Die Datenbank

7          Der Programmablauf

7.1   Bedienung des Programms

7.2   Probleme

7.2.1   Mangelhafte Bahntreue der Simulation

7.2.2   Probleme bei der Iteration

7.2.3   Ungenaue Ausgangsdaten

7.2.4   Zwischenzündungen

7.2.5   Weitere Fehlerquellen

7.3   Möglichkeiten der Weiterentwicklung

8          Anhang

8.1   Überblick über die verwendeten Symbole

8.2   Literaturverzeichnis

8.3   Quellenangaben

8.4   Erklärung

 


1            Einleitung

Die Aufgabe des Naturwissenschaften ist es, die Vielfalt der Phänomene in der uns umgebenden Welt durch möglichst einfache und kompakte allgemeingültige Gesetze zu begründen. Die grundlegenden theoretischen Methoden hierzu wurden zuerst von dem Engländer Sir Isaac Newton (1643-1727) konsequent angewandt, wodurch er als der Begründer der modernen Naturwissenschaften gilt.

Eines der vielen Interessengebiete dieses bedeutenden Mannes war die Astronomie. Er schaffte es, die damals bekannten Gesetze über die Planetenbewegungen, wie sie Johannes Kepler (1571-1630) aus Beobachtungen abgeleitet hatte, auf eine grundlegende Formel zurückzuführen.

Es wird die Geschichte erzählt, Newton sei unter einem Baum ein Apfel auf den Kopf gefallen, wodurch ihm die Idee gekommen sei, dass die Schwerkraft auf der Erde, die Körper zum Boden zieht, auf dem gleichen Gesetz beruht, das auch die Planetenbahnen bestimmt. Newton fand eine einfache Formel zur Beschreibung dieser Kraft, der Gravitation.

Bis heute hat diese Gesetzmäßigkeit Gültigkeit behalten. Für extreme Situationen wurde sie zwar durch die Relativitätstheorie Einsteins sowie die moderne Elementarteilchen- und Quantenphysik erweitert, doch bei normalen Verhältnissen reichen Newtons Methoden vollkommen.

So wird Newtons Gravitationsgesetz in der Raumfahrt benutzt, um Bahnen für Raumflugkörper zu berechnen. Dabei lässt sich aus dieser einfachen Formel auch ein Phänomen ableiten, das als Swing-by bezeichnet wird. Dabei gewinnt eine Sonde Energie durch den Einfluss des Gravitationsfeldes eines Planeten, was für Treibstoff- und Zeitersparnisse genutzt wird.

Diesen Effekt nutzend flogen die beiden 1977 gestarteten Voyager-Sonden von Planet zu Planet und übermittelten von dort neue Informationen an die Wissenschaftler auf der Erde. Inzwischen haben beide Sonden unser Sonnensystem verlassen und fliegen in den offenen Weltraum hinaus. Doch noch immer sind die Systeme der Sonden aktiv, übermitteln weiterhin wissenschaftliche Daten und nehmen neue Programmierungen entgegen. Bis zum Ende der Stromversorgung durch die Radionuklidbatterien (nach 2020) soll zumindest eine gewisse Grundfunktionalität aufrechterhalten bleiben.

Die Voyager-Sonden setzen eine schon zuvor mit den Sonden Pioneer 10 und 11, die als erste das Sonnensystem verlassen hatten, begonnene Tradition fort, eventuellen außerirdischen „Findern“ Informationen über ihre Herkunft zu vermitteln. Bestanden diese bei den Pioneer-Sonden aus einer einfachen Metallplakette, haben beide Voyager-Sonden eine Art Schallplatte an Bord, auf der jedoch neben Musik und Geräuschen auch Bilder gespeichert sind. Diese Datenträger erklären, wo wir sind, wer wir sind und wie wir leben. Eines der auf diesen Scheiben gespeicherten Bilder ist ein Foto von einer Seite aus einem Buch Newtons:[a]

Sie zeigt, wie Newton den Übergang von Wurfbahnen über Geschossflugbahnen zu den Bahnen von Himmelskörpern darstellt, alle auf der Basis des gleichen Gesetzes.

So schließt sich der Kreis zwischen Newton und Voyager, dessen eine Hälfte, den Weg von Newton zu Voyager, ich in dieser Arbeit beschreiben will.

Insbesondere die Energiegewinnung durch den Swing-by-Effekt soll untersucht und am konkreten Beispiel des Flugs der Raumsonde Voyager I bis zum Saturn demonstriert werden.

 

Ich widme diese Arbeit Isaac Newton, dem die Welt so vieles zu verdanken hat, sowie meiner Freundin Melanie, der ich so vieles zu verdanken habe und die immer wieder hören musste, dass ich wegen der Facharbeit keine Zeit für sie hätte.

Ed B. Massey, Steven E. Matousek und Enrique Medina, alle drei vom Jet Propulsion Laboratory der NASA, haben sich selbst um eine Widmung gebracht, indem sie auf keine meiner Anfragen geantwortet und mir damit die Arbeit sehr erschwert haben.

2            Das Gravitationsgesetz und seine Folgen

2.1       Die Grundlage: Newtons Gravitationsgesetz

Dem Problem der Gravitation kann man sich von zwei Seiten nähern. Ich werde in dieser Abhandlung den theoretischen Weg gehen, nicht den historischen, und die beobachtbaren Gesetzmäßigkeiten aus der ihnen zugrunde liegenden Formel herleiten, statt aus Beobachtung der Gesetzmäßigkeiten auf die Grundlage zu schließen.

Ich habe dieses Vorgehen gewählt, weil die Grundlage recht einsichtig ist und die daraus abgeleiteten Erkenntnisse über die an Planeten zu beobachtenden Gesetze hinausgehen, z.B. im Falle hyperbolischer Bahnen.

Für den historischen Weg verweise ich auf entsprechende Fachliteratur, z.B. Greiner, „Mechanik Teil 1“, S. 266ff. Ich folge in meinen Herleitungen auch weitgehend den Ausführungen Greiners.[b]

Als Grundlage der folgenden Untersuchungen setze ich also Newtons Gravitationsgesetz voraus:

 und                                                                               (1), (2)

Dies ist einfach zu erkennen als die Formel eine Zentralkraftfelds. Die Kraft nimmt mit der Masse jedes Körpers zu und mit dem Quadrat der Entfernung ab und zeigt dabei immer vom Körper auf den Koordinatenursprung, wo sich als Ursache der Kraft der andere Körper befindet. Die Gravitationskonstante [c] ist der gemessene Proportionalitätsfaktor.

Eine ähnliche Formel findet man auch bei anderen Zentralkräften wie beispielsweise der elektrostatischen Kraft nach dem Coulomb-Gesetz. Derartige Kraftfelder sind konservativ, d.h. die Arbeit zum Bewegen eines Körpers von A nach B in diesem Kraftfeld ist vom Weg der Bewegung unabhängig.

Im Weiteren kann man von zwei sehr unterschiedlichen Massen ausgehen, so dass der Einfluss des leichteren Körpers (Masse m) auf den schwereren (Masse M) vernachlässigt werden kann. Der schwere Körper wird als im Koordinatenursprung ruhend angenommen.

2.2       Drehimpuls

Das Drehmoment des um ein Zentrum kreisenden Körpers berechnet sich wie folgt: .

Der Drehimpuls ist analog definiert als .                                                    (3)

Da  ist, also die Komponente der Geschwindigkeit, die senkrecht auf dem Ortsvektor steht, ist L ohne Vektoren zu berechnen als .                                                  (4)

Außerdem gilt weshalb aus  die Tatsache folgt.             (5)

2.3       Flächensatz

Die Fläche, die der Ortsvektor pro Zeit überstreicht, lässt sich durch infinitesimal schmale Dreiecke beschreiben, mit dem Ortsvektor als einem und der Ortänderung in dieser Zeit als zweitem Seitenvektor. Dieses Flächendifferential ist , die Ableitung lautet also mit (3) und (5): .                                      (6)

Diesen Zusammenhang bezeichnet man als das 2. Kepler-Gesetz.

Für spätere Berechnungen nenne ich den Drehimpuls pro Masse h:

 unter Verwendung von (3) und (4)                                               (7)

Damit wird die pro Zeit überstrichene Fläche .                                                  (8)

2.4       Ebene Bewegung

Wegen  stehen  und  immer senkrecht auf . Da  konstant ist, muss die Bewegung in einer Ebene mit der Normalen  stattfinden. Für die Untersuchung der Bahnfigur reicht also die Betrachtung ebener Figuren.

2.5       Energie und Potential

Die Energie des Körpers ist .                                       (9)

Das Gravitationspotential V mit  als Bezugspunkt () ist durch  definiert als: . Dabei ist  die Komponente von  in Richtung , also die Radiusänderung. (im Unterschied zur Positionsänderung .) Man kann also statt dessen  schreiben. Das Integral lautet damit .

In (9) eingesetzt ergibt sich                                                            (10)

Die Geschwindigkeit v kann in eine radiale und eine tangentiale Komponente zerlegt werden, über Pythagoras ergibt sich mit (7): .

Die Gesamtenergie ist damit:

.                                                     (11)

Natürlich gilt der Energieerhaltungssatz.

2.6       Herleitung der Bahnfiguren

2.6.1     Umformungen

Mit diesen Voraussetzungen lassen sich jetzt die möglichen Formen der Flugbahnen berechnen. Ein Problem sind dabei die Ableitungen nach der Zeit, deshalb verwende ich (7), um die radiale Geschwindigkeit umzuformen:

                                                                                (12)

Einsetzen in die Energiegleichung (11) gibt:

.

2.6.2     Substitution

Es zeigt sich, dass durch Substitution von  leichter weiter zu rechnen ist.

Beachtet man, dass  ist,                                 (13)

so lässt sich die Gesamtenergie schreiben als:

(14)

2.6.3     Alternative Darstellung der Kraft

Nun wird die Kraft aus dem (unbekannten) Bewegungsmuster berechnet und in Newtons Formel eingesetzt, um eine Gleichung zu erhalten. Die Kraft wirkt in Richtung Zentrum, es lassen sich zwei Komponenten unterscheiden. Die eine, die Zentripetalkraft, ist nötig, um den Körper ohne Entfernungsänderung auf einer Kreisbahn zu halten, der Betrag dieser Kraft ist  (Allgemeine Formel der Zentripetalkraft bei einer Kreisbewegung). Die (evtl. negative) zweite Komponente der Kraft bewirkt Beschleunigungsarbeit in radialer Richtung auf den Planeten zu und damit eine radiale Entfernungsänderung. Da die „r-Achse“ vom Zentrum weg und damit F entgegen zeigt ist . Damit ist .

Etwas solider und trockener kann man diese Kraft auch mit Polarkoordinaten begründen. Wer mit obiger Begründung zufrieden ist, möge diese Herleitung überspringen.

Ich definiere zunächst den Einheitsvektor  in Richtung von  sowie  senkrecht dazu in Drehrichtung des Winkels . Abgeleitet nach der Zeit (Kettenregel) ergibt sich  und .

Der Ort des Körpers als Kombination dieser Vektoren ausgedrückt ist einfach .

Die Geschwindigkeit ist also (Produktregel) .

Die Beschleunigung ist somit (wieder Produktregel):

Da die Kraft jedoch auf den Koordinatenursprung gerichtet ist, müssen sich die Komponenten in Richtung  gegenseitig aufheben und ich brauche hier nur die Komponenten in Richtung  betrachten.

Die Kraft ist damit

Dabei zeigt  der Kraft entgegen und hat die Länge 1, wodurch der Betrag der Kraft wieder, wie schon zuvor anschaulich erklärt,  ist.

Auf diese Kraft F ist nun die Substitution anzuwenden. Für die erste Komponente wird mit (7) und der Substitution u:

Zur Berechnung von  muss zunächst  gebildet werden. Dazu wird in (12) das r mit Hilfe von (13) durch u ersetzt:

Erneutes Ableiten gibt mit der Kettenregel und (7):

Dadurch erhält man . Das kann man mit der Gravitationskraft nach Newton (1) gleichsetzen, wobei wieder die Substitution durchzuführen ist. Es ergibt sich die Differentialgleichung  und nach Kürzen:

                                                                                                            (15)

2.6.4     Das Lösen der Differentialgleichung

Gesucht wird eine Funktion , die diese Gleichung erfüllt. Man stelle sich vor, jemand will den Graphen dieser Funktion zeichnen. Er fängt bei einem Punkt  an. Den Funktionswert  sowie die Steigung  kann er vorerst frei wählen, die Änderung der Steigung  ergibt sich dann aus der Differentialgleichung. Damit ist der gesamte Verlauf der Funktion festgelegt. Man kann jetzt näherungsweise mit Hilfe von  die Kurve zeichnen, da ,  und  sich wieder aus der Differentialgleichung ergibt.

Dieser fiktive Zeichner hat also zwei Freiheiten, z.B.  und . Die Wahl von  selber stellt keine neue Freiheit dar, da man bei jeder Kurve einen beliebigen Punkt der Kurve als Startpunkt definieren kann, ein anderes  führt daher nicht zu neuen Funktionen.

Die Lösung für diese konkrete Differentialgleichung  (15) lautet:

.                                                                                               (16)

C und  sind dabei die beiden Freiheiten. Der einwandfreie mathematische Beweis, dass dies die einzige Lösung ist sowie die systematische Herleitung der Lösung sprengen den Rahmen dieser Arbeit. Hier muss es genügen, zu beweisen, dass diese Funktion die Gleichung erfüllt:

Dazu wird die Funktion zunächst abgeleitet:

                              (17)

Diese Ableitung kann nun zusammen mit der Funktion in die Differentialgleichung eingesetzt werden:

 q.e.d.

Bisher wurde für den Winkel  (also für das Polarkoordinatensystem) noch kein Nullpunkt festgelegt. Nun kann der Nullpunkt so gewählt werden, dass  wird und somit weggelassen werden kann.

Nun muss die Substitution rückgängig gemacht werden:

.

2.6.5     Deutung als Kegelschnittfigur

Nun ist aber die allgemeine Form einer Kegelschnittfigur in Polarkoordinaten:

                                                                                                         (18)

Dabei ist  die numerische Exzentrität, wodurch der Typ der Bahn festgelegt ist:

 ergibt einen Kreis,

 eine Ellipse,

 eine Parabel und

 eine Hyperbel.

Der Zusammenhang                                                                                  (19)

ist mit Vorsicht zu benutzen. Für den Kreis ist . Für die Ellipse ist a wie gewohnt die große Halbachse. Für die Parabel würde die Klammer Null werden und k wäre immer Null. Eine Parabel besitzt jedoch auch geometrisch kein a. (19) gilt deshalb nicht, k ist der Abstand zwischen Leitlinie und Fixpunkt. Für eine Hyperbel rechnet man üblicherweise mit , um für  in der Umgebung von 0° positive Radien zu erhalten.  ist die Hälfte der Strecke zwischen den Scheitelpunkten der beiden Hyperbeläste.

Man erkennt leicht, dass die Bahn eine Kegelschnittfigur darstellt mit

 und .                                                                                       (20)

Der Fall  lässt sich Problemlos mit den Ellipsen abhandeln. Der Fall  bedürfte im Prinzip eigener Behandlung. Dies ist jedoch ein Grenzfall, der nur theoretisch für einen einzigen Wert für  auftritt. Die Wahrscheinlichkeit, dass in der Natur genau dieser Wert auftritt, ist damit vernachlässigbar gering. Deswegen wird die Simulation nur Ellipsen und Hyperbeln berücksichtigen.

Da G und M positive Konstanten sind, ist auch k positiv. Damit wird es für die Hyperbel zwingend notwendig, dass das a in (19) negativ ist.

Für geometrische Betrachtungen ist auch die Größe b wichtig. Diese ist bei der Ellipse die kleine Halbachse mit . Bei der Hyperbel ist b nicht so leicht zu sehen. Am ehesten erkennt man es noch als y-Komponente des Asymptoten-Steigungsdreiecks im kartesischen Koordinatensystem. Die Asymptoten einer Hyperbel haben die Gleichung  mit , wodurch auch b ein negatives Vorzeichen hat. Allgemein kann man also schreiben:

                                                                                                               (21)

2.7       Energie und Bahnfigur

Um  zu errechnen, setze ich jetzt die Funktion u (16) und deren erste Ableitung (17) in die substituierte Energiegleichung (14) ein und löse nach C auf:

Anwendung der ersten binomischen Formel und Ausmultiplizieren gibt:

Wegen  ist  und .

Damit ist .

Da C Teil des Koeffizienten von  ist, bedeutet ein geändertes Vorzeichen von C eine Drehung des Koordinatensystems um 180° (). Deshalb kann das Koordinatensystem immer so gewählt werden, dass C positiv ist. Damit ist

                                                 (22)

Die Bahnform ist also von der Gesamtenergie abhängig. Mit , wie es dieser Formel zugrunde liegt, tritt der Grenzfall  (Parabel) für  ein.  führt zu  und damit zu einer Hyperbel,  zu  und damit zu einer Ellipse.

2.8       Geschwindigkeit

Man kann auch (22) nach E auflösen. Dazu wird zunächst mit (19) und (20) h durch a und  ausgedrückt:                                                                       (23)

Die Exzentrizität zum Quadrat ist damit

Wodurch sich die Energie  ergibt.

Es ist wieder zu beachten, dass  für .

Die Energie kann man wieder mit (10) in potentielle und kinetische Energie aufspalten und erhält  und damit:

                                                                                                        (24)

Bei einer Hyperbel ist die kinetische Energie eines Körpers im Unendlichen gleich der Gesamtenergie, da das Potential definitionsgemäß 0 wird. Es ist also

 und

3            Theorie des Swing-by

3.1       Qualitative Beschreibung des Vorgangs

Nachdem diese allgemeinen Gesetze jetzt untersucht sind, komme ich nun zum Swing-by-Effekt, wie er in der Raumfahrt genutzt wird, um Raumsonden zu beschleunigen. Die Raumsonde fliegt dabei in einem recht steilen Winkel auf einen Planeten zu und hinter ihm vorbei. Dabei wird sie durch das Gravitationsfeld des Planeten abgelenkt. Sie verlässt den Einflussbereich des Planeten in einer Richtung, die der Bewegungsrichtung des Planeten ähnlich ist.

Bei diesem Vorgang wird kinetische Energie vom Planeten auf die Sonde übertragen. Wegen des großen Masseunterschiedes ist eine Geschwindigkeitsänderung des Planeten trotzdem nicht nachzuweisen.

Die Flugbahnen der beiden Voyager-Sonden werden von der NASA so dargestellt:[d]

3.2       Annahmen und Näherungen

Wichtig für das Verständnis der folgenden Berechnungen ist das Prinzip, den Vorgang von zwei unterschiedlichen Standpunkten zu betrachten. System I ist der Standpunkt, der die Sonne als fest annimmt und die Bahnen der Körper betrachtet. Hier soll der Energiegewinn festgestellt werden. System II ist ein Beobachtungssystem, das sich mit dem Planeten mitbewegt.

Aufgrund der großen Entfernung von der Sonne wird außerdem die Gravitationskraft der Sonne als konstant betrachtet. Der Planet folgt ihr durch die Bahnkrümmung. Das System II folgt also dieser beschleunigenden Kraft, so dass ihre Wirkung im System nicht spürbar ist.

Wegen der kurzen Dauer des Vorgangs und der daraus folgenden räumlichen Begrenzung wird für System I angenommen, dass der Planet sich geradlinig und gleichförmig bewegt, während er in System II ruht und sich die Sonde nur im Gravitationsfeld des Planeten bewegt. System I und System II sind Inertialsysteme, die sich zueinander mit der Geschwindigkeit des Planeten, , bewegen.

Da sich die Sonde nicht in einer elliptischen Umlaufbahn um den Planeten befindet ist ihre Bahn eine Hyperbel (). Ich nehme an, dass sich die Sonde aus dem Unendlichen mit einer Geschwindigkeit  dem Planeten nähert und mit  seinen Einflussbereich wieder im Unendlichen verlässt.  bleibt dabei konstant. Ein Index kennzeichnet bei den Größen, welches System gemeint ist.

3.3       Berechnung des Energiegewinns

In System II findet eine elastische Streuung statt, es gelten also Energie- und Impulserhaltung.

Der Impulserhaltungssatz ist:  mit

Man kann nach  auflösen:

Der Energieerhaltungssatz lautet:

Mit  und dem  aus dem Impulserhaltungssatz ergibt sich:

 oder einfacher:

Wegen  ist  und damit  bzw. .

Die Geschwindigkeit der Sonde in System II ist also vor und nach der Begegnung annähernd gleich groß.

Um die Geschwindigkeiten ins System I zu übertragen, muss die Geschwindigkeit , mit der sich die Systeme zueinander bewegen, addiert werden:

, .

Der Geschwindigkeitsgewinn lässt sich hier durch graphische Vektoraddition gut sehen. Der Energiegewinn ergibt sich aus der kinetischen Energie:

Der Geschwindigkeitsgewinn wird maximal, wenn   entgegengesetzt gerichtet ist und  in Richtung von  zeigt. Dieser Fall ließe sich nur annäherungsweise erreichen. Die Asymptoten einer Hyperbel sind [e], der 360°-Wende könnte man sich also durch  nähern. Damit rutscht der Scheitel der Hyperbel immer näher an den Fixpunkt, was nicht endlos geht, weil die Sonde sonst in die Planetenatmosphäre eintauchen oder gar am Planeten zerschellen würde.

Doch auch sonst ist diese Überlegung sehr hypothetisch. Alle Planeten kreisen in gleicher Richtung um die Sonne, und wenn man eine Sonde von der Erde aus abschießt, wird diese zunächst auch in diese Richtung mitkreisen. Eine Richtungsänderung würde extrem viel Energie kosten. Daher ist die günstigste Flugbahn eine, bei der die Flugbahn des Planeten annähernd rechtwinklig getroffen wird. Da Raumsonden normalerweise ein anderes Ziel hat, als nur möglichst schnell möglichst weit in den Weltraum zu kommen, ist auch die Richtung des abgehenden Hyperbelastes nicht ganz frei wählbar, da die Sonde ein weiteres Ziel ansteuert. So flogen die Voyager-Sonden von einem Planet zum nächsten, um bei jeder Begegnung Geschwindigkeit zu gewinnen und den Planeten zu erforschen.

3.4       Voyager I als Beispiel

3.4.1     Allgemeines

Ich will nun die beschriebenen Techniken am Beispiel der Sonde Voyager I auf ihrem Flug am Jupiter vorbei zum Saturn betrachten. Diese Berechnungen haben noch wenig mit dem Computerprogramm zu tun.

Es wird mit geringer Genauigkeit gerechnet, nachdem es hauptsächlich um die Darstellung der Formelanwendung sowie um das qualitative Ergebnis geht. Die exakten Zahlenwerte sind nebensächlich. Die Planetenbahnen werden als Kreise angenommen.

Die verwendeten Daten stammen aus dem Internet:

Bahndaten:        http://vraptor.jpl.nasa.gov/voyager/vgrele_txt.html

Planetendaten:   http://nssdc.gsfc.nasa.gov/planetary/planetfact.html

3.4.2     Der Start

Die Masse der Sonne ist .

Der Radius der Erdbahn ist .

Für die Flugbahn von der Erde zum Jupiter ist .

Damit ist die Startgeschwindigkeit nach (24):

Da die Sonde jedoch von der Erde aus annähernd in Richtung der Erdbewegung gestartet wird, muss sie nicht aus der Ruhe beschleunigt werden.

Die Geschwindigkeit der Erde ist:

Die Sonde muss also „nur“ um ca.  beschleunigt werden.

3.4.3     Der Flug von der Erde zum Jupiter

Die Sonde folgt nun der Bahn  (18) mir  und . Über den Zwischenschritt  ergibt sich die Umkehrfunktion .

Die Sonde startet also im Perihel der Ellipse. Bis zum Jupiter (Bahnradius ) überstreicht der Fahrstrahl einen Winkel von

.

Das Verfahren zur Flächenberechnung wird in 4.4.2 erläutert, ich verwende hier schon die Ergebnisse dieser Überlegungen. Hier ist zuerst mit Gleichung (21) b auszurechnen: .

Das ergibt in (29) eingesetzt:

Mit dem so bestimmten  ist durch nach (28) aus 4.4.2 die Fläche:

Über (23) lässt sich h aus den Bahndaten ermitteln:

und damit . Schreibt man (8) als , so kann man t errechnen:

.

Der wahre Wert liegt nach Angabe der NASA etwa bei 560 Tagen. Wegen der geringen Rechengenauigkeit meiner Rechnungen ist diese Abweichung jedoch nicht weiter verwunderlich. Die Größenordnung stimmt jedenfalls.

3.4.4     Die Begegnung mit Jupiter

Ich verwende in diesem Abschnitt wieder die beiden oben beschriebenen Systeme.

Bis zu ihrer Begegnung mit Jupiter ist die Geschwindigkeit der Sonde gefallen auf

Die Geschwindigkeit des Jupiter ist analog zur Erdgeschwindigkeit zu berechnen:

In System II beschreibt die Sonde eine Hyperbelbahn um Jupiter. Die Form der Bahn wird in der Praxis durch den Winkel mit der Jupiterbahn sowie durch die Entfernung der Flugbahn zum Jupiter bestimmt. Es stehen jedoch wieder Bahndaten der NASA zur Verfügung, die  und  angeben, was mit (21) zu  führt. Der Winkel zwischen den Asymptoten ist somit:

Jupiter hat eine Masse von .

Die Asymptotische Geschwindigkeit im Unendlichen ist somit

Mit diesen Angaben kann man jetzt die Vektorfigur konstruieren:

Das so erhaltene Ergebnis von  lässt sich anhand der Daten für die nächste Flugphase zum Saturn kontrollieren. Für diese gilt  und damit

Das ist angesichts der geringen Rechengenauigkeit eine sehr gute Übereinstimmung.

Man könnte jetzt analog zum Flug von der Erde zum Jupiter auch die Zeit bis zur Begegnung mit Saturn ausrechnen. Doch ich denke eine Demonstration ist genug und verwende hier statt dessen die Daten aus der oben gezeigten NASA-Grafik. Demzufolge ist Voyager I am 5. September 1977 gestartet worden und hat am 12. November 1980 Saturn passiert. Das entspricht einer Flugzeit von 1164 Tagen (3,2 Jahre).

3.4.5     Der Direktflug zum Vergleich

Der günstigste Weg, eine Sonde direkt von der Erde zum Saturn zu bringen, wäre die sogenannte Hohmannbahn. Das ist eine Ellipse mit der Erde im einen und dem Saturn im anderen Scheitel. Die Sonne ist natürlich Fixpunkt und steht damit auf der Verbindungslinie der beiden Planeten.

Saturn hat etwa einen Bahnradius von , womit die große Halbachse der Hohmannbahn  ist.

Da die Sonne Fixpunkt ist, ist  und damit

. Dadurch ist die Ellipse bestimmt.

Zunächst einmal wieder die Startgeschwindigkeit:

, das entspricht also fast genau der Startgeschwindigkeit für die Flugbahn mit Swing-by.

Die kleine Halbachse der Hohmannbahn ist nach (21):

h wird für diese Bahn mit (23) zu

Nachdem die Ellipse von Scheitel zu Scheitel durchflogen wird, ist die vom Fahrstrahl überstrichene Fläche die Hälfte der gesamten Ellipsenfläche und somit

Damit ist

Trotz fast identischer Startgeschwindigkeit und somit gleichem Energie- und Treibstoffaufwand dauert der Direktflug 2,8 Jahre länger. Der Swing-by verkürzt also die Flugzeit auf 53% der Zeit, die für den Direktflug benötigt werden würde.

4            Problemlösungsansätze für das Programm

4.1       Koordinatensystem und Bahnelemente

4.1.1     Kartesische Koordinaten

Es gibt viele verschiedene Verfahren, um dreidimensionale Koordinaten von Objekten im Sonnensystem anzugeben. Das einfachste ist ein kartesisches Koordinatensystem mit der Sonne im Ursprung. Dieses System verwende ich für die Simulation selbst. Dabei handelt es sich um ein „rechte Hand System“. Nimmt man die rechte Hand und zeigt mit dem Daumen in x-Richtung und mit dem Zeigefinger in y-Richtung, so zeigt der Ringfinger in z-Richtung. Anders gesagt: Legt man in die xy-Ebene eine Zeichenebene, wie man sie von der zweidimensionalen Zeichenebene gewohnt ist (d.h. x nach rechts und y nach oben), so zeigt z aus der Ebene heraus auf den Betrachter.

Die x-Achse geht vom Ursprung aus in die Richtung, in der die Erde zum Zeitpunkt der Frühjahrs-Tagundnachtgleiche steht. Die xy-Ebene entspricht der Bahnebene der Erde, der sogenannten Ekliptik. Die z-Achse steht senkrecht dazu, und zwar so, dass sie von Süden nach Norden zeigt. (Diese Angaben entsprechen den Polen der Erde, d.h. der Nordpol der Erde liegt auf der Nordseite der Ekliptik.) Damit ist auch die y-Achse eindeutig festgelegt.

Aufgrund verschiedener Effekte, auf die ich hier nicht eingehen will, verändert die Erde im Laufe der Zeit langsam ihre Bahn. Es ist deshalb nötig, anzugeben, für welchen Zeitpunkt die Ekliptik und der Frühjahrspunkt festgelegt werden. Ich verwende das Jahr 2000. Man spricht von der Standardepoche J2000.0 und dem Koordinatensystem FK5.

Dieses System ist inzwischen der am weitesten verbreitete Standard und entspricht auch den Vorgaben der IAU (International Astronomical Union).


4.1.2     Längen- und Breitengrade

Ein weiteres verwendetes System ist von der Erde bekannt. Es handelt sich um das System von Längen- und Breitengraden. Dabei ist der Radius gegeben durch , die Länge durch  und die Breite durch  oder .

4.1.3     Bahnelemente mit Längengraden

Die Daten der Planetenbahnen beruhen auf diesem System. Es werden neben der großen Halbachse a und der Exzentrizität  noch folgende Größen angegeben:

§       Die Inklination: i. Das ist die Neigung der Bahnebene gegen die Ekliptik.

§       Die Länge des aufsteigenden Knotens (ascending node): . Das ist die Länge des Schnittpunkts, an dem die Bahn die Ekliptik mit zunehmender Breite, also von Süd nach Nord, schneidet.

§       Die Länge des Perihelpunktes: . Das ist die Länge des Punktes, wo der Planet den geringsten Abstand zur Sonne hat.

§       Die Länge des Planeten zu einem bestimmten Zeitpunkt (mean longitude): .

Der Zeitpunkt, für den die Daten gegeben sind, wird als Epoche bezeichnet.

4.1.4     Ekliptikale Bahnelemente

Es gibt noch eine weitere Form, um die Position einer Bahn im Sonnensystem anzugeben. Die Elemente dieses Formats sind , i,  und M (hier )

 ist die Länge des aufsteigenden Knotens, entspricht also dem .

Wie oben wird auch hier die Bahnneigung i angegeben.

Der dritte Winkel, , wird als Perihelargument bezeichnet. Er gibt den Winkel zwischen dem aufsteigenden Knoten und dem Perihelpunkt an. Da diese beiden Punkte sowie der Ursprung in der Bahnebene liegen, wird auch dieser Winkel automatisch entlang der Bahnkurve gemessen.

Statt der Position des Körpers zu einer bestimmten Zeit wird in diesem Format ein Winkel M angegeben, der als „mittlere Anomalie“ bezeichnet wird. Um diese Größe von der Masse des Zentralkörpers zu unterscheiden, werde ich sie hier mit  bezeichnen. Bei einem Körper, der sich auf einer elliptischen Bahn bewegt, stelle man sich einen fiktiven Körper vor, der mit der gleichen Umlaufdauer auf einer Kreisbahn um den Zentralkörper kreist.  wäre dann derjenige Winkel, den der fiktiver Körper seit dem Periheldurchgang des echten Körpers zurückgelegt hätte. Da sich ein Körper auf einer Kreisbahn mit konstanter Winkelgeschwindigkeit bewegt, ist dies ein Maß für die Zeit.

Es ist: . t ist die seit dem Periheldurchgang verstrichene Zeit. Die Umlaufdauer T ergibt sich mit (6) aus der Ellipsenfläche:  als .

Mit  (21) und  (23) erhält man .

Dadurch ist .

Bei einer Hyperbel macht es natürlich keinen Sinn, von einer Umlaufdauer zu reden. Trotzdem gilt per Definition ganz allgemein:

                                                                                                                 (25)

4.1.5     Anwendung ekliptikaler Eelemente

Mit diesen Daten kann nun die zweidimensionale Bahnkurve in den Raum übertragen werden. Das geht mit den ekliptikalen Elementen ganz leicht. Zunächst einmal die Ausgangssituation: Die Bahnkurve liegt in der xy-Ebene des Koordinatensystems. Die x-Achse zeigt dabei in Richtung des Perihels. Es folgt nun eine Folge von Rotationen, die man jeweils entweder als Rotation der Bahnkurve oder als entgegengesetzte Rotation des Koordinatensystems auffassen kann.

Unter der Rotation eines Vektors um eine Koordinatenachse verstehe ich folgendes: Der Fußpunkt des Vektors wird in den Koordinatenursprung gelegt. Entsprechend des verwendeten „rechte Hand Systems“ ist auch für die Drehrichtung die rechte Hand zu verwenden. Dabei Zeigt der Daumen die Richtung der Achse an. Die anderen Finger werden um die Achse gebogen, die Fingerspitzen geben die Drehrichtung an. Es ergeben sich die Drehrichtungen wie in der nebenstehenden Abbildung.

Zunächst wird die Figur um den Winkel  um die z-Achse gedreht. In Richtung der x-Achse liegt jetzt der aufsteigende Knoten, die Figur liegt weiterhin in der xy-Ebene. Es folgt eine Drehung um den Winkel i um die x-Achse. Dadurch wird die Bahnneigung realisiert. Es folgt eine weitere Rotation um die z-Achse, diesmal um den Winkel . Damit rutscht der aufsteigende Knoten und mit ihm die gesamte Figur an ihre endgültige Position.

Der Winkel  wird mit (25) in die seit dem Periheldurchgang verstrichene Zeit umgerechnet, diese wird von der Epoche abgezogen und somit der Zeitpunkt des Periheldurchgangs ermittelt. Dieser wird für die Zeitangaben verwendet.

4.1.6     Umwandlung des Längengradformates

Das Format mit den Längengraden muss erst auf ekliptikale Elemente umgerechnet werden. Dazu nutze ich folgende Hilfskonstruktion:

Ich definiere ein neues Koordinatensystem mit der x-Achse in Richtung des aufsteigenden Knoten. Gesucht wird jetzt . Wie aus der Grafik ersichtlich gilt:

            (26)

Auf gleiche Weise lässt sich auch der Winkel  in einen Winkel in der Bahnebene umwandeln. Es ist der Winkel zwischen diesem Punkt und dem Perihel interessant, dieser Winkel wird als „wahre Anomalie“  bezeichnet ( ist synonym zu ):

4.2       Das Format der Daten

4.2.1     Planetendaten

Ursprünglich war das Programm konzipiert, die Bahnelemente der NASA-Seite

http://nssdc.gsfc.nasa.gov/planetary/planetfact.html

zu nutzen. Daran orientiert sich das System der Längengradangaben. Es hat sich inzwischen herausgestellt, dass diese Angaben zu ungenau sind.

Es gibt genaue Verfahren, zu jedem Zeitpunkt die Position eines Planeten auszurechnen. Dies funktioniert mit einer VSOP87 (= Variations Séculaires des Orbites Planétaires) genannten Theorie, die von P. Bretagnon und G. Francou 1987 entwickelt worden ist.[f] Prof. Manfred Dings von der Hochschule des Saarlandes für Musik und Theater hat diese Theorie zu einer Computerbibliothek (DLL = Dynamic Link Library) verarbeitet und zur Verfügung gestellt.[g] Die so zu gewinnenden Daten sind kartesische Koordinaten mit der Ekliptik und dem Frühjahrspunkt des entsprechenden Datums. Nach der Umwandlung mittels der in 4.1.2 genannten Formeln in Polarkoordinaten kann die Umrechnung auf FK5 erfolgen, wozu ich Formeln von Meeus verwende.[h] Mittels Intervallschachtelungen (binäre Suche u.ä.) werden die Daten nach Null- und Extremstellen durchsucht. Der gesamte Algorithmus wurde von mir unter Microsoft® Excel mit Visual Basic for Applications (VBA) realisiert, da dies einen einfachen Zugriff auf die DLL ermöglicht. Da der Algorithmus nicht zum eigentlichen Thema der Facharbeit gehört, will ich nicht näher auf ihn eingehen. Das Makro findet sich mitsamt den Ergebnissen in einer Excel-Datei auf der beiliegenden CD.

4.2.2     Voyager Bahndaten

Die Voyager-Bahndaten finden sich im Internet unter

http://vraptor.jpl.nasa.gov/voyager/vgrele_txt.html

Dort werden für die einzelnen Phasen des Voyager-Flugs ekliptikale Bahnelemente angegeben. Die Angabe „Epoche“ bezeichnet den Zeitpunkt, für den diese Werte gelten. Die Abkürzung „ET“ für „Ephemeris Time“ drückt nur aus, dass ein gleichmäßiger Zeitmaßstab genommen wird, die durch eine Atomuhr und nicht durch erdspezifische Faktoren wie den Sonnenstand definiert wird. Von diesen Schwankungen abgesehen entspricht ET der Weltzeit GMT (Greenwich Mean Time).

Da griechische Zeichen in HTML-Dokumenten Probleme mit sich bringen, wird auf lateinische Buchstaben ausgewichen. e steht für , OM für  und o für .

Die Bahnelemente werden in einem anderen Koordinatensystem angegeben, was sich auf das Besselsche Jahr B1950.0 bezieht und als FK4 bezeichnet wird. Ich verwende folgende Umrechnung[i]:

                                                             (27)

Die dabei verwendeten Winkel sind ,  und .

4.3       Prinzip der Interpolation

Um die Körper simulieren zu können braucht man einen Zusammenhang zwischen der Zeit und dem Ort eines Körpers. Um aus der Zeit den Ort zu errechnen, wie es nahe liegt, gibt es jedoch keine elementarmathematische Lösung, nur iterative Näherungsverfahren (siehe Meeus Kapitel 29), die recht kompliziert und rechenaufwendig sind.

Statt dessen kann man aus der Fahrstrahlfläche über  (8) leicht auf die Zeit schließen, zu der sich der Körper an diesem Punkt befindet. Ich berechne also zu einer Reihe von Orten die entsprechende Zeit und führe mit dem so entstandenen „Datenskelett“ eine lineare Interpolation aus, um den Ort zu einer gegebenen Zeit verhältnismäßig exakt und trotzdem leicht zu bestimmen. Die Berechnung der „Skelettdaten“ kann vor Start der Simulation erfolgen, was auch bei vielen Skelettpunkten die Simulation noch recht schnell ablaufen lässt.

Ich brauche jetzt also einen Zusammenhang zwischen dem Ort und der Fläche A. Diese Berechnung führe ich je nach gegebener Situation unterschiedlich durch.

4.4       Flächenberechnungen

4.4.1     Affine Abbildungen

Für die hier dargestellten Flächenberechnungen sind sogenannte Affinitäten von besonderer Bedeutung. Dabei handelt es sich um geometrische Abbildungen mit der Vorschrift . In einfacher Koordinatenform ausgedrückt bedeutet das:  und . Nachdem das Verhältnis der Flächen untersuchen werden soll, ist noch folgende Beziehung wichtig: .[j]

4.4.2     Elliptische Bahnen

Die Parameterform einer Ellipse ist ;  mit . Der Fixpunkt, in dem der Planet liegt, hat die Koordinaten . Man kann die Figur um des Faktor a in x-Richtung und um den Faktor b in y-Richtung stauchen () und mit 1 für a und b weiter rechnen. Alle Flächen werden dabei um den Faktor ab kleiner. Die Ellipsenpunkte werden abgebildet auf  mit  und , also einen Kreis mit dem Radius 1. Das Abbild des Fixpunktes ist . Die Fläche, die P mit dem Perihelpunkt und dem Mittelpunkt einschließt ist . Es wird jedoch die Fläche bezüglich des Fixpunktes benötigt. Daher ist die Dreiecksfläche  zu subtrahieren. Am Ende ist die Fläche noch wieder mit ab zu multiplizieren, was zu der Fläche  führt. (28)

Kennt man wie z.B. bei den Planetendaten nur die wahre Anomalie , so lässt sich p bestimmen: .                         (29)

Setzt man r aus den Polarkoordinaten (18) mit dem k aus (19) hier ein, so erhält man

Damit lässt sich zu einem Winkel die seit dem Periheldurchgang verstrichene Zeit ausrechnen.

4.4.3     Hyperbolische Bahnen

Mein Programm berücksichtigt Hyperbeln nur für Voyager-Bahnen, nicht für Planeten.

Die Parameterform eines Hyperbelastes ist ;  mit , was wegen  zu einer Figur wie in der Abbildung führt. Der Scheitel liegt bei , der Fixpunkt bei .

Man kann auch diese Figur um des Faktor a in x-Richtung und um den Faktor b in y-Richtung stauchen () und mit 1 für a und b weiter rechnen. Die Bildfigur liegt auf der rechten Seite der y-Achse. Die Flächeninhalte der Bildfigur sind um den Faktor ab kleiner als die der Originalfigur. Der Scheitel liegt jetzt bei , der Fixpunkt bei . Dies ist wohlgemerkt nicht mehr der geometrische Fixpunkt der Bildfigur, sondern das Abbild des ursprünglichen Fixpunktes, also des Planeten. Für die Bildfigur gilt: . Die Asymptoten schließen einen Winkel von 90° ein.

Zur Einfachheit nehme ich jetzt , also nur eine Hälfte des Astes (). Die untere Hälfte ist ja symmetrisch. Um beide Asthälften zu berücksichtigen rechnet man einfach mit  und setzt vor die Ergebnisfläche das Vorzeichen von .

Dreht man die Figur um 45° gegen den Urzeigersinn, ergibt sich  und  (). Flächeninhalte haben sich dabei nicht geändert, der Scheitel ist zu  gerutscht. Zum Integrieren muss er jedoch bei (1/1) liegen. Die Figur wird also noch einmal um den Faktor  gestreckt (), weshalb das Ergebnis mit dem Faktor  multipliziert werden muss, um den Effekt aller Transformationen auszugleichen. Man kann auch die zweite und die dritte Abbildung zu einer Drehstreckung zusammenfassen, um sich die Wurzeln zu sparen (). Der Fixpunkt ist jetzt bei  gelandet. Inzwischen ist  und  oder einfach . Jetzt kann man integrieren:

.

Ich definiere: , , , , , .
Damit ist die gesuchte Fläche A durch Trapeze zu berechnen:

.

Die wirkliche Fläche ergibt sich schließlich durch Multiplikation mit , bei negativem  ist noch ein Minus davor zu setzen.

4.5       Die schrittweise Simulation

Nachdem für mehr als zwei Körper, die sich auch noch zueinander bewegen, das Problem der Bahnkurve extrem kompliziert bis unlösbar wird, wird für das Computerprogramm nicht die Bahn berechnet, sondern die Bewegung simuliert. Ausgangspunkt sind Planeten, die sich auf festen Bahnen bewegen, und ein Körper, auf den die Anziehungskraft all dieser Körper wirkt.

Die Sonde startet zum Zeitpunkt mit einer Anfangsgeschwindigkeit  am Ort . Die Beschleunigung, die nun von einem Planeten auf die Sonde wirkt, ist nach (2) , wobei  der Vektor vom Planeten (Position ) zur Sonde ist. Die Beschleunigungen sind wie Kräfte additiv: .

Man erhält näherungsweise  und .

Theoretisch müsste man, um möglichst exakte Werte zu erhalten,  so klein wie möglich machen. Praktisch entstehen dabei jedoch zwei Probleme. Zum einen werden mehr Iterationen erforderlich, und zum anderen entstehen für sehr kleine  mehr Rundungsfehler, da extrem kleine Zahlen zu sehr großen Zahlen addiert werden. Es ist also nötig, einen Kompromiss zu finden.

Ich habe mich entschlossen, keine feste Rechengenauigkeit anzugeben, sondern  von der Stärke der wirkenden Beschleunigungskräfte abhängig zu machen. Im Programm wird eine Größe „TimestepBase“ eingelesen. Sie dient als Grundlage für folgende Rechnung: .

Das bedeutet, je größer die Beschleunigung wird, desto kleiner werden die Zeitschritte und desto genauer wird damit die Rechnung. Ändert sich die Situation schnell, wird genau gerechnet, langsamere Veränderungen erlauben gröbere Rechnung. Damit läuft die Simulation in Planetennähe langsam und in Planetenferne schnell, genau umgekehrt also zur wirklichen Fluggeschwindigkeit.

4.6       Die Gliederung in Phasen

Der Flug der Sonde wird in mehrere Phasen unterteilt. Für eine Phase wird angegeben, welcher Punkt im Raum in den Bildschirmmittelpunkt gerückt werden soll so wie der Maßstab der Darstellung. Am Anfang und am Ende jeder Phase wird die Simulation angehalten, so dass man am Anfang die Objektpositionen als Punkte und am Ende die in dieser Phase geflogenen Bahnen sehen kann.

Da die Anziehungskraft von Planeten sich meist nur auf sehr kurze Entfernung hin bemerkbar macht, ist es unnötig, für alle Planeten die durch diese verursachte Beschleunigung auszurechnen. Man kann also für jede Phase angeben, welche Planeten in dieser Phase zur Gravitationsberechnung berücksichtigt werden sollen.

4.7       Die Darstellung

Die Darstellung des Programms ist höchst einfach, da aufwendige Grafik das Programm unnötig kompliziert machen und von seinem physikalischen Kern ablenken würde. Die Körper werden durch Punkte oder Kreise auf dem Schirm dargestellt, bei der Bewegung werden alte Positionen nicht gelöscht, so dass Bahnspuren entstehen. Der Bildschirm zeigt dabei einen Ausschnitt der Projektion der Körper in die xy-Ebene.

Durch den modularen Aufbau des Programms und die Verfügbarkeit genauer dreidimensionaler kartesischer Koordinaten sind sehr viel aufwendigere Darstellungsmöglichkeiten denkbar. Ich denke dabei ganz besonders an von Raytracern erstellte dreidimensionale photorealistische Animationen. Es wäre leicht, das Programm dazu zu veranlassen, Szenenbeschreibungen für beispielsweise POV-Ray zu erstellen. Es ist also eine gute Erweiterbarkeit gegeben. Sollte sich jemand diese Arbeit machen, bitte ich darum, davon in Kenntnis gesetzt zu werden.

4.8       Die Datenbank

Die Datenbank, die Das Programm verwendet, besteht aus mehreren Datenblöcken. Ein Block wird durch „[Blocktyp]“ eingeleitet. Die Daten folgen dann als Datenelemente in der Form „Name=Wert“. Jeder Block besitzt ein obligatorisches Datenelement, das „name“ heißt. Durch dieses wird der Block eindeutig identifiziert. Die anderen Datenelemente unterscheiden sich je nach Blocktyp. In einem Block wird oft auch auf einen anderen Block verwiesen, was mithilfe des Namens geschieht.

Der Aufbau der einzelnen Blöcke wird bei den Erklärungen zu den entsprechenden Klassen erläutert. Die Datenbank selber findet sich unter 6.13, wodurch der beschriebene Aufbau klarer wird.

5            Programmierung mit C++

5.1       Warum C++

Ich habe diese Facharbeit in C++ geschrieben. Diese Sprache erschien mir am besten geeignet, die Problematik klar zu formulieren. Außerdem erfreut sich C++ weiter Verbreitung und ist eine der wichtigsten Programmiersprachen überhaupt.

Der von mir verwendete C-Compiler ist DJGPP, die DOS-Version des GNU C Compilers, und damit unentgeltlich zu haben. Der Compiler befindet sich auch auf der CD, ebenso ein Programm, das die Funktionen der Programmbibliothek erklärt.

Für die Grafikausgabe verwende ich die GRX Grafikbibliotheken, die ebenfalls frei verfügbar sind.

5.2       Grundsätzliches zu C und C++

Hier in ungeordneter Reihenfolge einige wichtige Grundzüge, um Programme in C und C++ zu verstehen. Die genannten Eigenschaften von C betreffen natürlich auch C++.

§       C verwendet viele geschweifte Klammern. Wo andere Sprachen Wörter wie BEGIN und END haben, sind in C nur Klammern. Das macht die Programmierung kompakt und zeigt trotzdem deutlich die Struktur. Ich schreibe die { immer an das Ende der Anweisung, die den Block beschreibt, etwa eine if-Abfrage. Der folgende Teil wird eingerückt, die } hat wieder den gleichen Einzug wie die Anweisung am Beginn. Enthalten die Klammern nur einen einzigen Befehl, kann man meist auf die Klammern verzichten. Das ist unabhängig von Zeilenwechseln. Ich schreibe in solchen Fällen entweder in der gleichen Zeile weiter oder rücke die nächste Zeile auch ohne Klammern ein.

§       C verwendet = als Zuweisungsoperator und == als Vergleichsoperator. && steht für das logische UND, || für ODER, ! für NICHT. != bedeutet UNGLEICH. C kennt keinen eigenen Datentyp für Wahrheitswerte. 0 wird als falsch gewertet, jeder andere Wert als Wahr.

§       In C werden hin und wieder einzelne Bits angesprochen. Zu diesem Zweck gibt es die bitweisen Operatoren & (AND), | (OR), ^ (XOR), ~ (NOT). Der Ausdruck var&3 beispielsweise liefert einen von Null verschiedenen Wert, falls das erste oder zweite Bit von var gleich 1 ist.

§       Kommentare werden in C++ üblicherweise durch // eingeleitet. Ein Kommentar erstreckt sich dabei bis zum Zeilenende.

§       Typdeklarationen stehen in C vor dem Variablennamen. In C++ müssen die Deklarationen nicht notwendiger Weise am Funktionsanfang stehen. Die von mir verwendeten Datentypen sind:

-         int, unter GNU C ein 32Bit Integer

-         unsigned, kurz für unsigned int

-         long, immer 32Bit Integer, aus Kompatibilitätsgründen so bezeichnet

-         double, 64Bit Gleitkommazahl

-         long double, 96Bit Gleitkommazahl

-         char, ein Zeichen (8Bit)

-         mtime, ein von mir definierter Typ, synonym zu long double

-         boolean, von mir für Wahrheitswerte definiert, synonym zu int

§       Ein Typ kann in einen anderen Konvertiert werden, indem vor den zu konvertierenden Ausdruck der neue Typ in Klammern geschrieben wird. Bei der Umwandlung von Gleitkommazahlen in Ganzzahlen werden einfach alle Nachkommastellen weggelassen. So ist (int)(-1.5) == -1.

§       Funktionen haben in C normalerweise einen Rückgabewert. Ist dies nicht der Fall, wird void als Rückgabetyp angegeben. Der Rückgabetyp steht konsequent vor dem Funktionsnamen. Nach der Funktionsbezeichnung kommen die Parameter, voneinander durch Kommata getrennt, jeweils erst Typ dann Name. Es folgt der Programmcode der Funktion in geschweiften Klammern.

§       In C spielen Pointer (=Zeiger) eine große Rolle. Ein Pointer ist eine Variable, die eine Speicheradresse enthält. Bei der Deklaration wird ein Pointer durch einen * nach dem Typbezeichner bzw. vor dem Variablennamen gekennzeichnet. Ein * vor dem Variablennamen kann im Programm dazu verwendet werden, um auf die Daten zuzugreifen, auf die der Pointer zeigt. Um die Adresse einer Variable zu erhalten wird ein & vor den Variablennamen gesetzt.

§       Pointer werden auch für Arrays unbestimmter Größe verwendet. Der Pointer enthält dabei die Adresse des ersten Arrayelements. Der Zugriff auf ein Arrayelement funktioniert über var[i], wobei die Zählung bei 0 beginnt. Gewöhnungsbedürftig ist die Tatsache, dass es in C keinen Datentyp für Zeichenketten gibt. Diese werden stattdessen als Array vom Typ char verwaltet. Ein Zeichen mit dem ASCII-Wert 0 kennzeichnet dabei das Ende des Strings.

§       Das Programm beginnt mit Aufruf der Funktion main. Diese hat üblicherweise die Form int main (int argc, char **argv). argv sind dabei die Parameter, die dem Programm beim Aufruf auf der Kommandozeile mitgegeben werden. Es ist ein Array von Arrays von Zeichen, also ein Array von Strings. argv[0] ist der Name der Programmdatei, argv[1] der erste optionale Parameter. argc gibt an, wie viele Elemente argv enthält. Der Rückgabewert ist üblicherweise 0 im Falle einer fehlerfreien Ausführung.

§       Eine erklärungsbedürftige Struktur ist die Form von for-Schleifen in C:
for (a; b; c) { d; }       ließe sich auch schreiben als
a; while (b) { d; c; }   . Eine übliche Form z.B. beim arbeiten mit einem Array mit 3 Elementen:

for (int i=0; i<3; i++) {

 array[i]+=7;

}

Die Variable i wird hier neu definiert und hat nur innerhalb der Schleife Gültigkeit. Man kann aber selbstverständlich auch externe Variablen verwenden.

§       In dem Schleifenbeispiel tauchen auch zwei weitere elegante C-Schreibweisen auf. Die Anweisung i++ erhöht den Wert von i um 1. +=7 bedeutet die Erhöhung einer Variable um 7. Analoges gilt für alle vier Grundrechenarten.

§       Ab und zu kommt im Programm das Schlüsselwort const vor. Dessen technische Bedeutung ist hier unwichtig und kann ignoriert werden.

§       In C können über #define Konstanten definiert werden. Diese werden schon vor dem Compilieren in den Text eingesetzt, als ob sie an dieser Stelle stehen würden. Daher können nicht nur Zahlen so definiert werden, sondern ganze Ausdrücke. Deshalb werden #define-Anweisungen auch nicht mit einem Strichpunkt beendet. Es ist auch möglich, Parameter an so definierte Ausdrücke zu übergeben. Man spricht dann von einem sogenannten Makro. Ein Beispiel aus meinem Programm ist das Makro sqr, welches das Quadrat einer Zahl durch Multiplikation mit sich selbst bildet.

#define sqr(x) ((x)*(x))

§       Ein sehr wichtiger Bestandteil von C ist das Schlüsselwort #include. Die danach angegebene Datei wird an dieser Stelle des Programms eingefügt. So eingefügte Dateien haben üblicherweise die Endung .h wie header. So steht am Anfang meiner .cc-Dateien (für C++) ein #include "voyager.h", Diese Datei wiederum enthält #include-Anweisungen für alle anderen Headerdateien. In den .h-Dateien stehen die Klassendefinitionen, in den .cc-Dateien werden die Funktionen implementiert.

5.3       Objektorientierte Programmierung

5.3.1     Grundlagen der objektorientierten Programmierung

C++ zeichnet sich im Unterschied zu normalem C durch die Objektorientierte Programmierung aus. Dazu muss zunächst der Begriff der Klasse (=class) erklärt werden. Eine Klasse ist eine Art benutzerdefinierter Datentyp. Er kann mehrere Eigenschaften (=members) enthalten. Das sind Variablen einfacher oder anderer zusammengesetzter Typen. Derartige Strukturen sind in praktisch allen Hochsprachen vorhanden, in Pascal in Form der RECORDs, in Standard C in Form von structs.

Klassen unterscheiden sich von diesen dadurch, dass sie zusätzlich zu den Eigenschaften auch noch sogenannte Methoden (=methods) enthalten können. Das sind Funktionen, die mit den Eigenschaften der Klasse arbeiten. Ein Objekt ist eine „Variable“, die eine Klasse als Datentyp hat, also eine Instanz der Klasse.

Es ist in C++ üblich, Klassennamen mit „C“ zu beginnen und Eigenschaftsnamen mit „m_“. Zugegriffen wird auf diese Elemente mit einem Punkt. Eine Methode wird wie eine gewöhnliche Funktion geschrieben, nur dass vor den Methodennamen der Klassenname und :: angehängt wird.

Ein Beispiel:

class CMyClass {

 public:

  int m_value;

  int set (int value);

  int restore { m_value=m_backup; }

 protected:

  int m_backup;

};

 

int CMyClass::set (int value) {

 m_backup=m_value;

 m_value=value;

 return m_backup;

}

 

int main(int argc, char **argv) {

 int x=3;

 CMyClass obj;

 obj.m_value=42;

 x=obj.set(x);

 // jetzt ist x=42 und obj.m_value=3

}

An diesem Beispiel kann man viel sehen. Es wird klar, wie eine Klasse definiert wird.

public bedeutet, dass die folgenden Elemente von außen verfügbar sind, während protected-Elemente nur von Methoden dieser Klasse zu verwenden sind. So könnte main nicht auf obj.m_backup zugreifen.

Die Funktion kann, wenn sie kurz ist, auch direkt in der Klassendefinition implementiert werden, wie restore im Beispiel. Der Compiler behandelt so angegebene Funktionen etwas anders, aber das ist hier irrelevant.

5.3.2     Vererbung

Eine Klasse kann von einer anderen Klasse abgeleitet werden. Dabei erbt die abgeleitete Klasse alle Eigenschaften und Methoden der Basisklasse, kann sie aber um eigene Methoden ergänzen. Außerdem kann die abgeleitete Klasse auch Methoden der Basisklasse neu implementieren, wobei diese Methoden aus technischen Gründen in der Basisklasse mit dem Schlüsselwort virtual gekennzeichnet werden sollten.

Einer Funktion, die einen Pointer auf ein Objekt der Basisklasse definiert, kann auch einen Pointer auf ein Objekt der abgeleiteten Klasse übergeben bekommen, von der dann die geerbten Elemente angesprochen werden können. Im Falle von neu implementierten Methoden wird die Version der abgeleiteten Klasse verwendet.

Es ist also sinnvoll, Objektklassen mit ähnlichen Eigenschaften von einer gemeinsamen Basisklasse abzuleiten und alle Gemeinsamkeiten dort zu verwalten.

Es kann sein, dass eine solche Basisklasse nur ein Interface, eine Art Fassade, bereitstellt. Sie gibt also an, welche Methoden es gibt, welche Parameter diese erwarten u.ä., aber nicht was die Methoden im einzelnen tun. In einem solchen Fall kann die Methode in der Klassendefinition mit „=0“ gekennzeichnet werden, man kann dann auf die Implementierung verzichten. Von einer solchen Klasse können jedoch nicht direkt Objekte erstellt werden. Dies ist nur von abgeleiteten Klassen möglich, die diese Methoden implementiert. Man spricht von einer abstrakten Basisklasse.

5.3.3     Konstruktor

Ein Konstruktor ist eine Methode, die den gleichen Namen hat wie die Klasse. Diese Methode wird automatisch beim Anlegen eines neuen Objekts dieser Klasse aufgerufen. Der Konstruktor hat die Aufgabe, das neue Objekt zu initialisieren.

Da ein Konstruktor auch mit Parametern aufgerufen werden kann, ist es auch möglich, ein Objekt gleich mit bestimmten Startwerten anzulegen.

Wird eine Objekt einer abgeleiteten Klasse angelegt, so wird zuerst der Konstruktor der Basisklasse und dann der der abgeleiteten Klasse ausgeführt. Besitzt die Basisklasse keinen Konstruktor, der ohne Parameter auskommt (Standardkonstruktor), so ist es nötig, anzugeben, welche Parameter an den Basisklassenkonstruktor übergeben werden. Dies geschieht, indem bei der Implementierung des Konstruktors der abgeleiteten Klasse nach der Parameterliste durch einen Doppelpunkt getrennt der Konstruktor der Basisklasse samt Parametern angegeben wird. Beispiele finden sich genug im Programm.

5.3.4     Statische Elemente

Eigenschaften von Klassen können mit dem Schlüsselwort static gekennzeichnet sein. Damit wird diese Variable einmal für die ganze Klasse und nicht für jedes einzelne Objekt angelegt. Der Effekt ist praktisch der einer globalen Variable, jedoch durch die Definition innerhalb einer Klasse wird die Bedeutung klarer, außerdem kann der Zugriff eingeschränkt werden. Auf statische Eigenschaften kann auch zugegriffen werden, wenn von dieser Klasse gar kein Objekt existiert. Dafür kann die Syntax CKlasse::m_variable verwendet werden.

Methoden, die nur mit statischen Eigenschaften arbeiten, also nicht an ein einzelnes Objekt gebunden sind, können auch als static deklariert werden. Auch diese können dann über den Klassennamen aufgerufen werden.

Innerhalb von Funktionen können Variablen als static deklariert werden. Diese Variablen bleiben nach Beendigung der Funktion erhalten, haben also beim nächsten Aufruf wieder den selben Wert.

5.3.5     Kettenstrukturen

Ich verwende in meinem Programm viele Kettenstrukturen, weshalb ich diese hier erläutern möchte.

Grundlage einer Kettenstruktur ist ein zusammengesetzter Datentyp (class oder struct), der einen Pointer auf Objekte des eigenen Typs enthält. Ein Objekt dieses Typs stellt praktisch ein Kettenglied dar. Der Pointer stellt die Verbindung zum nächsten Glied der Kette her. Das Ende der Kette wird durch einen NULL-Pointer angezeigt. Das ist ein Pointer auf die Speicheradresse 0, an der keine Daten stehen können und der deshalb allgemein für ungültige bzw. nicht gesetzte Pointer verwendet wird. Um auf die Kette zugreifen zu können ist es nötig, irgendwo einen Zeiger auf das erste Glied zu speichern.

Der große Vorteil solcher Kettenstrukturen liegt in der Speicherplatzbelegung. Im Gegensatz zu einem Array muss die Größe nicht von Anfang an bekannt sein. Wird ein neues Element benötigt, wird einfach ein weiteres Glied an die Kette angehängt.

6            Das Programm

6.1       Übersicht über die Hierarchie der Klassen

Diese Grafik zeigt alle Klassen, die in meinem Programm vorkommen, sowie welche Klassen von welchen abgeleitet sind.

Die Vererbung gilt insbesondere bei Klassen, die von CData abgeleitet sind und somit Datenblöcke in der Datenbank beschreiben. Ich werde im Folgenden jeweils nur die neuen Datenelemente nennen, die Elemente der jeweiligen Basisklasse werden ohne Ausnahme übernommen.

6.2       Allgemeine Definitionen

Jede .cc-Datei lädt zuerst die Datei voyager.h. In dieser werden alle für das Programm wichtigen Informationen geladen. Ganz wichtig sind hierbei die Definitionen aller verwendeten Klassen. Die Datei voyager.h sieht folgendermaßen aus:

// Allgemeine Definitionen

 

// Header-Dateien für die verwendeten C-Funktionen

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <ctype.h>

#include <sys/types.h>

#include <regex.h>

#include <math.h>

#include <conio.h>

#include <grx20.h> // Header-Datei für die Grafikfunktionen

 

// Einstellungen

#define MAX_LINE 1024          // Maximal 1kByte pro Zeile...

#define MIN_PREC 36            // Mindestens 36 Punkte Datenskelett

#define DEF_PREC 720           // Standardmässig 720 Punkte

#define MAX_PREC 1300000       // Maximal 1,3Mio Punkte

#define DEF_TIMESTEP_BASE .5   // Zeitschrittgrundlage (wird durch a geteilt)

#define DEF_DISPSTEP 10        // Neu zeichnen nach x Iterationen (Standard)

 

// Konstanten

#undef PI // Definition aus math.h ist etwas ungenauer.

#define PI (3.1415926535897932384626433832795)    // PI ganz genau

#define DEG (.0174532925199432957692369076848861) // PI/180

#define GRAV (6.67259E-11)   // Aus P.M., siehe Quellenangaben

#define TRUE (1)                                  // 1 <> 0 => Wahr

#define FALSE (0)                                 // 0 = Falsch

 

// Datentypen

typedef long double mtime; // Millenium time, Sekunden nach 1.1.2000 12:00 TD

typedef int boolean;       // Damit man Wahrheitswerte als solche erkennt

 

// Makro

#define sqr(x) ((x)*(x)) // Quadrat

 

// globale Variable

extern class CVector scrctr; // Bildschirmmittelpunkt

 

// inline-Funktion

inline void GrSetPixel (int x, int y, GrColor color) {

 GrPutScanline (x, x, y, &color, GrWRITE);

}

 

// Definitionen aller verwendeten Klassen in richtiger Reihenfolge

#include "Vector.h"

#include "DataElement.h"

#include "Data.h"

#include "Point.h"

#include "Mass.h"

#include "Planet.h"

#include "Path.h"

#include "Init.h"

#include "Phase.h"

6.3       Die Klasse CVector

6.3.1     Allgemeines über überladene Operatoren

Ein großer Vorteil der objektorientierten Programmierung mit C++ ist die Möglichkeit, eigene Klassen sehr flexibel zur Definition neuer Datentypen einzusetzen. Dies wird auch dadurch unterstützt, dass es möglich ist, für Rechenoperatoren in Verbindung mit Klassen eigene Rechenregeln aufzustellen. Dies geht beispielsweise für den Additionsoperator durch eine Methode namens operator+. Dieses Prinzip hat es mir ermöglicht, Vektoren als vielseitigen Datentyp zu implementieren, mit dem ganz normal die Grundrechenarten durchgeführt werden können, ohne dass man sich um eine besondere Syntax kümmern müsste.

6.3.2     Quellcode

6.3.2.1          Vector.h

// Definition der Klasse CVector

 

class CVector {

 public:

  // Vektorkomponenten sind public und damit einzeln verfügbar

  long double x, y, z; // Extrem genaue Gleitkommazahlen

 

  // Standardkonstruktor liefert den Nullvektor

  CVector()                                   { x=y=z=0.; }

  // Kopierkonstruktor erlaubt das kopieren des Objekts

  CVector(const CVector &v)                   { x=v.x; y=v.y; z=v.z; }

  // Genaue angabe zwei- oder dreidimensionaler Koordinaten

  CVector(double ix, double iy, double iz=0.) { x=ix; y=iy; z=iz; }

 

  // Zuweisung

  CVector &operator= (const CVector &v)       { x=v.x; y=v.y; z=v.z; return *this; }

  // Grundrechenarten

  CVector operator* (double s) const          { return CVector(x*s, y*s, z*s); }

  CVector &operator*= (double s)              { x*=s; y*=s; z*=s; return *this; }

  CVector operator/ (double s) const          { return CVector(x/s, y/s, z/s); }

  CVector &operator/= (double s)              { x/=s; y/=s; z/=s; return *this; }

  CVector operator+ (const CVector &v) const  { return CVector(x+v.x, y+v.y, z+v.z); }

  CVector &operator+= (const CVector &v)      { x+=v.x; y+=v.y; z+=v.z; return *this; }

  CVector operator- (const CVector &v) const  { return CVector(x-v.x, y-v.y, z-v.z); }

  CVector &operator-= (const CVector &v)      { x-=v.x; y-=v.y; z-=v.z; return *this; }

  CVector operator- () const                  { return CVector(-x, -y, -z); }

  // Vergleich

  boolean operator== (const CVector &v) const;

 

  // Nur der Form wegen: nachträgliches festlegen der Werte

  void set(double ix, double iy, double iz=0) { x=ix; y=iy; z=iz; }

  // Betrag des Vektors

  double abs() const;

  // Einheitsvektor

  CVector direction() const                   { return operator/(abs()); }

  // Rotation um die Achsen (für die Vollständigkeit auch y-Achse)

  CVector rotx(double a) const;

  CVector roty(double a) const;

  CVector rotz(double a) const;

};

6.3.2.2          Vector.cc

// Implementation der Klasse CVector

 

#include "voyager.h"

 

// Vergleichsoperator

boolean CVector::operator== (const CVector &v) const {

 return (x==v.x && y==v.y && z==v.z);

}

 

// Betrag

double CVector::abs() const {

 return sqrt(sqr(x)+sqr(y)+sqr(z));

}

 

// Rotation

CVector CVector::rotx(double a) const {

 return CVector (x, y*cos(a)-z*sin(a), y*sin(a)+z*cos(a));

}

 

CVector CVector::roty(double a) const {

 return CVector (x*cos(a)+z*sin(a), y, -x*sin(a)+z*cos(a));

}

 

CVector CVector::rotz(double a) const {

 return CVector (x*cos(a)-y*sin(a), x*sin(a)+y*cos(a), z);

}

6.4       Die Klasse CDataElement und davon abgeleitete

6.4.1     Zugriff auf die Datenelemente der Datenbankblöcke

Die Klasse CDataElement ist die Basisklasse für eine ganze Reihe von kleinen Hilfsklassen. Jede einzelne stellt einen Datentyp dar, der in der Datenbank verwendet wird. Den einzelnen in den Blöcken vorkommenden Datenelemente entsprechen jeweils einem von CDataElement abgeleiteten Objekt. Die zu einem Block gehörenden Datenelemente werden dabei als Kette organisiert.

Datenelemente werden in den Konstruktoren der Blockklassen durch einen Aufruf „bestellt“, der typischerweise wie folgt aussieht:

new CDETyp (&m_element, "Bez", &m_var [, OPTIONAL]);

CDETyp ist dabei die dem erforderlichen Datentyp entsprechende Klasse.

m_element ist eine Eigenschaft der Klasse CData, in der das erste Kettenglied gespeichert wird. Diese Speicherung übernimmt CDataElement selbst, man muss also nur die Adresse von m_element übergeben.

Bez ist der Name des Datenelements.

m_var ist die Variable, in der die Daten gespeichert werden sollen.

Ist die Angabe dieses Datenelements nicht zwingend notwendig, so wird dies auch angegeben. Sonst muss der letzte Parameter nicht angegeben werden. Zwingend erforderliche Parameter werden bei den Blockbeschreibungen fett gedruckt.

Diese Angaben werden in den Eigenschaften m_name (für Bez) und m_var sowie m_optional gespeichert. m_var unterscheidet sich dabei je nach Typ. In jeder abgeleiteten Klasse gibt es eine Funktion parse(char *value), die die in der Datenbank vorgefundene Zeichenfolge entsprechend dem Datentyp interpretiert und speichert.

Da diese Klasse wenig mit Physik zu tun hat, ist ihr Verständnis nicht so wichtig.

6.4.2     Quellcode

6.4.2.1          DataElement.h

// Definition der Klasse CDataElement und aller davon abgeleiteten Klassen CDExxxxx

 

// Optional leicht als solches kenntlich

#define OPTIONAL TRUE

 

// Rückgabewert für CDataElement::assign

#define CDE_ASSIGN_OK (0)

#define CDE_ASSIGN_NOELEM (1)

#define CDE_ASSIGN_TWICE (2)

#define CDE_ASSIGN_PARSE (3)

 

// Diese Datei wird vor Data.h geladen,

// daher ist Data.h derzeit noch unbekannt,

// daher muss ich erstmal erklären, DASS es diese Klasse gibt.

class CData;

 

// Basisklasse aller Datenelemente

class CDataElement {

 public:

  // Standardkonstruktor, übernimmt auch die Eingliederung in die Kette.

  CDataElement(CDataElement** ptr, const char *name, boolean optional=FALSE);

 

  // Überladener Arrayoperator, um über den namen auf die Kette zugreifen zu können

  // Durchsucht rekursiv die Kette

  CDataElement *operator[](const char *name);

 

  // Zuweisen eines Datenbankeintrags an die richtige Variable (rekursiv)

  boolean assign (const char *name, char *value);

 

  // Können noch mehr Daten verkraftet werden?

  virtual boolean wantMoreData() { return !m_havedata; }

 

  // Sind unbedingt noch Daten erforderlich? (rekursiv)

  boolean needMoreData();

 

  // Verknüpft die Blöcke untereinander (rekursiv)

  // von CDELink und CDELinklist überladen

  virtual boolean link();

 

  // Datenelement ist Optional, Daten wurden eingelesen

  boolean m_optional, m_havedata;

 

 protected:

  // String ins Datenformat konvertieren (von assign aufgerufen, immer überladen)

  virtual int parse (char *value)=0;

  CDataElement *m_next; // nächstes Kettenglied

  char *m_name;         // Name dieses Datenelements

};

 

// Datentyp für Gleitkommazahlen

class CDEReal : public CDataElement {

 public:

  CDEReal(CDataElement** p, const char *n, double *var, boolean o=FALSE);

 

 protected:

  double *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Ganzzahlen

class CDEInteger : public CDataElement {

 public:

  CDEInteger(CDataElement** p, const char *n, long *var, boolean o=FALSE);

 

 protected:

  long *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Zeichenfolgen

class CDEString : public CDataElement {

 public:

  CDEString(CDataElement** p, const char *n, char **var, boolean o=FALSE);

 

 protected:

  char **m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Winkelangaben

class CDEAngle : public CDataElement {

 public:

  CDEAngle(CDataElement** p, const char *n, double *var, boolean o=FALSE);

 

 protected:

  double *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Entfernungsangaben

class CDEDistance : public CDataElement {

 public:

  CDEDistance(CDataElement** p, const char *n, double *var, boolean o=FALSE);

 

 protected:

  double *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Masseangaben

class CDEMass : public CDataElement {

 public:

  CDEMass(CDataElement** p, const char *n, double *var, boolean o=FALSE);

 

 protected:

  double *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Zeitpunkte

class CDETime : public CDataElement {

 public:

  CDETime(CDataElement** p, const char *n, mtime *var, boolean o=FALSE);

 

 protected:

  mtime *m_var;

  virtual boolean parse (char *value);

  mtime mkmtime (int year, int month, double day);

};

 

// Datentyp für Zeitspannen

class CDETimeSpan : public CDataElement {

 public:

  CDETimeSpan(CDataElement** p, const char *n, mtime *var, boolean o=FALSE);

 

 protected:

  mtime *m_var;

  virtual boolean parse (char *value);

};

 

// Datentyp für Verbindungen zu anderen Datenblöcken

// Erlaubter Blocktyp wird als linktype im Konstruktor übergeben:

// linktype und getType() des Blocks müssen mindestens ein Bit gemeinsam gesetzt haben.

class CDELink : public CDataElement {

 public:

  CDELink(CDataElement** p, const char *n, unsigned linktype,

          CData** var, boolean o=FALSE);

  virtual boolean link(); // Findet den Pointer anhand des Namens

 

 protected:

  unsigned m_linktype; // Typ des Links

  CData **m_var;

  char *m_linkname;    // Name des Links

  virtual boolean parse (char *value);

};

 

// Datentyp für einen Verweis auf mehrere Blöcke

class CDELinkList : public CDataElement {

 public:

  CDELinkList(CDataElement** p, const char *n, unsigned linktype,

              CData*** var, boolean o=FALSE);

  int length() { return m_length; } // Anzahl der gefundenen Blöcke

  // Linklisten können auf mehrere Zeilen aufgeteilt werden => Immer Daten annehmen

  virtual boolean wantMoreData() { return TRUE; }

  virtual boolean link(); // Findet die Pointer anhand der Namen

 

 protected:

  unsigned m_linktype; // Typ der Links

  CData ***m_var;

  struct NameChain {

   char *name;

   NameChain *next;

  } *m_linknames;      // Kette für die Linknamen

  int m_length;        // Anzahl der gefundenen Links

  virtual boolean parse (char *value);

};

 

// Datentyp für Farbangaben (Rot, Grün, Blau, jeweils 0-255)

class CDEColor : public CDataElement {

 public:

  CDEColor(CDataElement** p, const char *n, GrColor *var, boolean o=FALSE);

  // Konstruktor zur Angabe einer Farbvoreinstellung

  CDEColor(CDataElement** p, const char *n, GrColor *var,

           int r, int g, int b, boolean o=FALSE);

  // Farben können erst im Grafikmodus zuverlässig angelegt werden

  // Dazu wird diese Funktion aufgerufen, die die Kette abarbeitet.

  static void alloc();

 

 protected:

  // speichert die Farbwerte bis zum Aufruf von alloc()

  int m_rgb[3];

  GrColor *m_var;

  virtual boolean parse (char *value);

  static CDEColor *m_firstcolor; // Kopf der Kette aller CDEColor-Objekte

  CDEColor *m_nextcolor;         // Nächstes Glied der Kette

};

6.4.2.2          DataElement.cc

// Implementierung der Klasse CDataElement und aller davon abgeleiteten Klassen CDExxxx

 

#include "voyager.h"

 

///////////////////////////////////////////////////////////////////////////////

// class CDataElement

 

// Konstruktor Fügt dieses Objekt in die Liste ein, auf deren Kopf ptr zeigt

CDataElement::CDataElement(CDataElement** ptr, const char *name, boolean optional) {

 if (*ptr==NULL) *ptr=this; // Liste leer => das hier wird das erste Glied

 else {

  CDataElement *p;

  for (p=*ptr; p->m_next!=NULL; p=p->m_next); // Letztes Glied suchen

  p->m_next=this;                 // Dieses Glied dranhängen

 }

 m_next=NULL;                     // Das hier ist das letzte Glied

 m_name=new char[strlen(name)+1]; // Speicher für den Namen belegen

 strcpy (m_name, name);           // Namen in den Speicher kopieren

 m_optional=optional;             // Parameter speichern

 m_havedata=FALSE;                // Immer leer anfangen

}

 

// Ermöglicht den Zugriff auf ein Datenelement durch dessen Namen

CDataElement *CDataElement::operator[](const char *name) {

 // Ist dieses Objekt das gesuchte?

 if (!strcasecmp(name, m_name)) return this;

 // Ist hier das Ende der Liste?

 if (m_next==NULL) return NULL;

 // Sonst Rest der Liste durchsuchen

 return (*m_next)[name];

}

 

// Sucht rekursiv das zum Namen passende CDE-Objekt und speichert die Daten darin

boolean CDataElement::assign (const char *name, char *value) {

 // Ist dies das gesuchte Datenelement?

 if (!strcasecmp(name, m_name)) {

  // Können hier noch Daten gebraucht werden?

  if (!wantMoreData()) return CDE_ASSIGN_TWICE;

  if (parse(value)) { // Wenn Daten erkannt wurden (Rückgabewert TRUE)

   m_havedata=TRUE;   // Wir haben Daten

   return CDE_ASSIGN_OK;

  }

  else return CDE_ASSIGN_PARSE;

 }

 // Wenn dies nicht das gesuchte Element ist: Listenende?

 if (m_next==NULL) return CDE_ASSIGN_NOELEM;

 // Sonst Rest der Liste durchsuchen

 return m_next->assign(name, value);

}

 

// Braucht der rest der Kette nich unbedingt Daten?

boolean CDataElement::needMoreData() {

 if (!(m_optional || m_havedata)) { // Weder optional noch Daten da

  printf ("%s wurde nicht angegeben\n", m_name); // Fehlermeldung

  // Trotzdem rest der Kette durchsuchen, falls noch mehr Daten fehlen

  if (m_next!=NULL) m_next->needMoreData();

  return TRUE;

 }

 // Rest der Kette durchsuchen, falls das hier nicht das Kettenende ist

 if (m_next==NULL) return FALSE;

 return m_next->needMoreData();

}

 

// Rekursiv für jedes Kettenglied link() aufrufen

boolean CDataElement::link() {

 if (m_next==NULL) return TRUE;

 return m_next->link();

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEReal

 

CDEReal::CDEReal(CDataElement** p, const char *n, double *var, boolean o)

        :CDataElement(p,n,o) {

 m_var=var;

}

 

// Einfache Zahl im Format -####.####E-###

boolean CDEReal::parse (char *value) {

 char *unit;

 *m_var=strtod(value, &unit);

 if (unit==value) { // Beginnt nicht mit einer Ziffer

  printf ("%s: Kein g\201ltiges Zahlenformat: %s.\n", m_name, value);

  return FALSE;

 }

 if (*unit!='\0') {

  printf ("%s: Zeilenende statt Einheit erwartet: %s.\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEInteger

 

CDEInteger::CDEInteger(CDataElement** p, const char *n, long *var, boolean o)

           :CDataElement(p,n,o) {

 m_var=var;

}

 

// Einfache Ganzzahl -##### , ggf. auch oktal: 0#### oder hexadezimal: 0x####

boolean CDEInteger::parse (char *value) {

 char *unit;

 *m_var=strtol(value, &unit, 0); // Erkennt automatisch verwendetes Zahlensystem

 if (unit==value) { // Beginnt nicht mit einer Ziffer

  printf ("%s: Kein g\201ltiges Zahlenformat: %s.\n", m_name, value);

  return FALSE;

 }

 if (*unit!='\0') {

  printf ("%s: Zeilenende statt Einheit erwartet: %s.\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEString

 

CDEString::CDEString(CDataElement** p, const char *n, char **var, boolean o)

          :CDataElement(p,n,o) {

 m_var=var;

}

 

// Wert wird direkt gespeichert

boolean CDEString::parse (char *value) {

 *m_var=new char[strlen(value)+1]; // Speicherplatz belegen

 strcpy(*m_var, value); // Zeichenfolge in den Speicher kopieren

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEAngle

 

CDEAngle::CDEAngle(CDataElement** p, const char *n, double *var, boolean o)

         :CDataElement(p,n,o) {

 m_var=var;

}

 

// Gültige Einheiten: °, deg, Degree, gra, Grad, rad, Radian, ...

boolean CDEAngle::parse (char *value) {

 char *unit;

 *m_var=strtod(value, &unit);

 if (unit==value) {

  printf ("%s: Keine Zahl: %s.\n", m_name, value);

  return FALSE;

 }

 while (isspace(*unit)) unit++; // Leerzeichen vor Einheit löschen

 if (*unit=='\0') {

  printf ("Warnung: %s: Keine Einheit angegeben, gehe von GRAD aus.\n", value);

  (*m_var)*=DEG;

 }

 else if (!strncasecmp(unit, "deg", 3) || !strncasecmp(unit, "gra", 3) ||

          ((unit[0]=='°' || unit[0]=='\370') && unit[1]=='\0'))

  (*m_var)*=DEG;

 else if (!strncasecmp(unit, "rad", 3)); // Keine Änderung nötig

 else {

  printf ("%s: Unbekannte Winkeleinheit: %s\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEDistance

 

CDEDistance::CDEDistance(CDataElement** p, const char *n, double *var, boolean o)

            :CDataElement(p,n,o) {

 m_var=var;

}

 

// Gültige Einheiten: m, km, AU, AE

boolean CDEDistance::parse (char *value) {

 char *unit;

 *m_var=strtod(value, &unit);

 if (unit==value) {

  printf ("%s: Keine Zahl: %s.\n", m_name, value);

  return FALSE;

 }

 while (isspace(*unit)) unit++;

 if (*unit=='\0') printf ("Warnung: %s: Keine Einheit angegeben, gehe von m aus.\n", value);

 else if (!strcasecmp(unit, "m"));

 else if (!strcasecmp(unit, "km"))

  (*m_var)*=1E3;

 else if (!strcasecmp(unit, "au") || !strcasecmp(unit, "ae"))

  (*m_var)*=1.495979E11;

 else {

  printf ("%s: Unbekannte L\204ngeneinheit: %s\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDEMass

 

CDEMass::CDEMass(CDataElement** p, const char *n, double *var, boolean o)

        :CDataElement(p,n,o) {

 m_var=var;

}

 

// Gültige Einheiten: g, kg, t

boolean CDEMass::parse (char *value) {

 char *unit;

 *m_var=strtod(value, &unit);

 if (unit==value) {

  printf ("%s: Keine Zahl: %s.\n", m_name, value);

  return FALSE;

 }

 while (isspace(*unit)) unit++;

 if (*unit=='\0') printf ("Warnung: %s: Keine Einheit angegeben, gehe von kg aus.\n", value);

 else if (!strcasecmp(unit, "kg"));

 else if (!strcasecmp(unit, "t"))

  (*m_var)*=1E3;

 else if (!strcasecmp(unit, "g"))

  (*m_var)*=1E-3;

 else {

  printf ("%s: Unbekannte Masseneinheit: %s\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDETime

 

CDETime::CDETime(CDataElement** p, const char *n, mtime *var, boolean o)

        :CDataElement(p,n,o) {

 m_var=var;

}

 

// Diese Funktion ist komplizierter, da sie mit sogenannten

// Regular Expressions arbeitet. Eine Erklärung zu diesen gibt es bei der Referenz

// zur Funktion regcomp. Die erlaubten Formate stehen in der Funktion

boolean CDETime::parse (char *value) {

 regex_t re;

 regmatch_t match[7];

 int i;

 char buf[32];

 int val[6];

 int year, month;

 double day;

 

 // Format yyyy-mm-dd hh:mm:ss

 if (regcomp(&re,

     "^(-?[0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) +"

     "([0-2]?[0-9])[.:]([0-9]{1,2})[.:]([0-9]{1,2})?$",

     REG_EXTENDED)) {

  printf ("internal error in RE1\n");

  exit(1);

 }

 if (!regexec(&re, value, 7, match, 0)) {

  for (i=1; i<7; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   val[i-1]=atoi(buf);

  }

  year=val[0];

  month=val[1];

  day=(((double)val[5]/60.+(double)val[4])/60.+(double)val[3])/24.+(double)val[2];

  mkmtime (year, month, day);

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 // Format yyyy mm dd.ddd

 if (regcomp(&re,

     "^(-?[0-9]{1,4}) ([0-9]{1,2}) ([0-9]{1,2}\\.[0-9]{0,29})$",

     REG_EXTENDED)) {

  printf ("internal error in RE2\n");

  exit(1);

 }

 if (!regexec(&re, value, 4, match, 0)) {

  for (i=1; i<4; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   val[i-1]=atoi(buf);

  }

  year=val[0];

  month=val[1];

  day=atof(buf);

  mkmtime (year, month, day);

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 // Format mm/dd/yyyy hh:mm:ss

 if (regcomp(&re,

     "^([0-9]{1,2})/([0-9]{1,2})/(-?[0-9]{1,4}) +"

     "([0-2]?[0-9])[.:]([0-9]{1,2})[.:]([0-9]{1,2})$",

     REG_EXTENDED)) {

  printf ("internal error in RE3\n");

  exit(1);

 }

 if (!regexec(&re, value, 7, match, 0)) {

  for (i=1; i<7; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   val[i-1]=atoi(buf);

  }

  year=val[2];

  month=val[0];

  day=(((double)val[5]/60.+(double)val[4])/60.+(double)val[3])/24.+(double)val[1];

  mkmtime (year, month, day);

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 // Format dd.mm.yyyy hh:mm:ss

 if (regcomp(&re,

     "^([0-9]{1,2})\\.([0-9]{1,2})\\.(-?[0-9]{1,4}) +"

     "([0-2]?[0-9])[.:]([0-9]{1,2})[.:]([0-9]{1,2})$",

     REG_EXTENDED)) {

  printf ("internal error in RE4\n");

  exit(1);

 }

 if (!regexec(&re, value, 7, match, 0)) {

  for (i=1; i<7; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   val[i-1]=atoi(buf);

  }

  year=val[2];

  month=val[1];

  day=(((double)val[5]/60.+(double)val[4])/60.+(double)val[3])/24.+(double)val[0];

  mkmtime (year, month, day);

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 printf ("%s: Unbekannte Zeitangabe: %s\n", m_name, value);

 return FALSE;

}

 

// Funktion, die das aus dem Format ausgelesene Datum in mtime umwandelt

// Verwendet den von Meeus auf Seite 73ff. dargestellten Algorithmus

// zur Berechnung des julianischen Datums

mtime CDETime::mkmtime (int year, int month, double day) {

 if (month<=2) {

  year--;

  month+=12;

 }

 double a=floor(year/100.);

 double b=2.-a+floor(a/4);

 long double jd=floor(365.25*(year+4716));

 jd+=floor(30.6001*(month+1));

 jd+=day+b-1524.5; // Julianisches Datum fertig

 // J2000.0 abziehen und von Tagen auf Sekunden umrechnen

 (*m_var)=(jd-2451545.)*86400.;

 return *m_var;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDETimeSpan

 

CDETimeSpan::CDETimeSpan(CDataElement** p, const char *n, mtime *var, boolean o)

            :CDataElement(p,n,o) {

 m_var=var;

}

 

// Diese Funktion ist komplizierter, da sie mit sogenannten

// Regular Expressions arbeitet. Eine Erklärung zu diesen gibt es bei der Referenz

// zur Funktion regcomp. Neben dem Format ddd hh:mm:ss.sss

// sind noch die folgenden Einheiten gültig:

// d, day, days, Tag, Tage, ...

// h, hour, hours, Std, Stunde, ...

// m, min, minute, ...

// s, sec, second, Sekunde, ...

boolean CDETimeSpan::parse (char *value) {

 regex_t re;

 regmatch_t match[6];

 int i;

 char buf[32];

 int val[4];

 

 // Format ddd hh:mm:ss.sss

 if (regcomp(&re,

     "^([0-9]+)d? *([0-2]?[0-9])[.:]([0-9]{1,2})[.:]([0-9]{1,2})(\\.[0-9]{0,31})?$",

     REG_EXTENDED)) {

  printf ("internal error in RE5\n");

  exit(1);

 }

 if (!regexec(&re, value, 6, match, 0)) {

  for (i=1; i<5; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   val[i-1]=atoi(buf);

  }

  *m_var=((val[0]*24.+val[1])*60.+val[2])*60.+val[3];

  if (match[5].rm_so!=match[5].rm_eo) {

   strncpy(buf, value+match[5].rm_so, match[5].rm_eo-match[5].rm_so);

   buf[match[5].rm_eo-match[5].rm_so]='\0';

   (*m_var)+=atof(buf);

  }

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 char *unit;

 *m_var=strtod(value, &unit);

 if (unit==value) {

  printf ("%s: Keine Zahl: %s.\n", m_name, value);

  return FALSE;

 }

 while (isspace(*unit)) unit++;

 if (*unit=='\0') {

  printf ("Warnung: %s: Keine Einheit angegeben, gehe von d aus.\n", value);

  (*m_var)*=86400.;

 }

 else if (!strncasecmp(unit, "d", 1) || !strncasecmp(unit, "Tag", 3)) (*m_var)*=86400.;

 else if (!strncasecmp(unit, "h", 1) || !strncasecmp(unit, "St", 2)) (*m_var)*=3600.;

 else if (!strncasecmp(unit, "m", 1)) (*m_var)*=60.;

 else if (!strncasecmp(unit, "s", 1));

 else {

  printf ("%s: Unbekannte Zeiteinheit: %s\n", m_name, unit);

  return FALSE;

 }

 return TRUE;

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDELink

 

CDELink::CDELink(CDataElement** p, const char *n,

                 unsigned linktype, CData** var, boolean o)

        :CDataElement(p,n,o) {

 m_var=var;

 m_linktype=linktype;

 m_linkname=NULL;

}

 

// parse speichert erstmal nur den Namen

boolean CDELink::parse (char *value) {

 m_linkname=new char[strlen(value)+1];

 strcpy(m_linkname, value);

 return TRUE;

}

 

// link stellt die tatsächliche Verbindung her.

boolean CDELink::link() {

 if (m_havedata) {

  // Sucht das entsprechende CData-Objekt

  *m_var=CData::find(m_linkname, m_linktype);

  if (*m_var==NULL) {

   printf ("%s: Angegebener Block %s konnte nicht gefunden werden.\n",

           m_name, m_linkname);

   return FALSE;

  }

  delete [] m_linkname; // Speicher für Namen freigeben

 }

 return CDataElement::link(); // Basisklassenaufruf zur Rekursion

}

 

///////////////////////////////////////////////////////////////////////////////

// class CDELinkList

 

CDELinkList::CDELinkList(CDataElement** p, const char *n,

                         unsigned linktype, CData*** var, boolean o)

            :CDataElement(p,n,o) {

 m_var=var;

 m_linktype=linktype;

 m_linknames=NULL;

 m_length=0;

}

 

// Namen werden durch die Trennzeichen Kommata, Semikola oder Leerzeichen getrennt.

// Die Namen werden in einer Kettenstruktur gespeichert

boolean CDELinkList::parse (char *value) {

 NameChain *nc;

 char *name;

 

 name=strtok(value, ",; "); // Beim ersten Trennzeichen aufspalten

 // Keinen einzigen Namen gefunden (String leer oder nur Trennzeichen)?

 if (name==NULL) return FALSE;

 

 if (m_linknames==NULL) nc=m_linknames=new NameChain; // Erstes Kettenglied?

 else {

  for(nc=m_linknames; nc->next!=NULL; nc=nc->next);   // Kettenende suchen

  nc->next=new NameChain;                             // neues Kettenglied anlegen

  nc=nc->next;                                        // Das neue Glied bearbeiten:

 }

 nc->name=new char[strlen(name)+1];                   // Speicher belegen

 strcpy(nc->name, name);                              // Namen kopieren

 

 // Für alle weiteren Namen neue Kettenglieder aufspalten ...

 while (name=strtok(NULL, ",; ")) {

  nc->next=new NameChain;

  nc=nc->next;

  nc->name=new char[strlen(name)+1];

  strcpy(nc->name, name);

 }

 nc->next=NULL; // Kettenende nach dem letzten Element

 return TRUE;   // Erfolg, mindestens ein Name gemerkt

}

 

// link stellt die Verbindungen her

boolean CDELinkList::link() {

 NameChain *nc, *nc2=NULL; // Pointer auf zwei Namenskettenglieder

 int i=0;

 

 // Alle Namen zählen

 for (nc=m_linknames; nc!=NULL; nc=nc->next) m_length++;

 // Speicher für alle Namen anlegen

 *m_var=new (CData*)[m_length+1];

 // Kette abarbeiten

 for (nc=m_linknames; nc!=NULL; nc=nc2) {

  // Datenblock suchen

  (*m_var)[i]=CData::find(nc->name, m_linktype);

  if ((*m_var)[i]==NULL) {

   printf ("%s: Angegebener Block %s konnte nicht gefunden werden.\n",

           m_name, nc->name);

   return FALSE;

  }

 

  i++;                // Schleifenzähler rauf

 

  // nc2 zeigt auf das nächste Glied, damit das hier gelöscht werden kann.

  // In der for-Anweisung wird nc2 dann nach nc kopiert

  nc2=nc->next;

  delete [] nc->name; // Speicher für Namen löschen

  delete nc;          // Glied selber löschen

 }

 (*m_var)[i]=NULL;    // NULL am Listenende

 return CDataElement::link(); // Basisklassenaufruf für Rekursion

}

 

 

///////////////////////////////////////////////////////////////////////////////

// class CDEColor

 

// Statische Variable (erstes Element) initialisieren

CDEColor *CDEColor::m_firstcolor=NULL;

 

CDEColor::CDEColor(CDataElement** p, const char *n, GrColor *var, boolean o)

         :CDataElement(p,n,o) {

 m_var=var;

 

 // An den Anfang der Liste einfügen (geht schneller, Reihenfolge unwichtig)

 m_nextcolor=m_firstcolor;

 m_firstcolor=this;

 m_rgb[0]=m_rgb[1]=m_rgb[2]=128; // 50% Grau wenn nichts anderes vorgegeben

}

 

// Analog, speichert aber angegebene Farbvoreinstellungen

CDEColor::CDEColor(CDataElement** p, const char *n, GrColor *var,

                   int r, int g, int b, boolean o)

         :CDataElement(p, n, o) {

 m_var=var;

 m_nextcolor=m_firstcolor;

 m_firstcolor=this;

 m_rgb[0]=r;

 m_rgb[1]=g;

 m_rgb[2]=b;

}

 

// Diese Funktion ist komplizierter, da sie mit sogenannten

// Regular Expressions arbeitet. Eine Erklärung zu diesen gibt es bei der Referenz

// zur Funktion regcomp. Die erlaubten Formate stehen in der Funktion

boolean CDEColor::parse (char *value) {

 regex_t re;

 regmatch_t match[4];

 int i;

 char buf[16];

 

 // Ganzzahlig 0-255

 // Format rrr,ggg,bbb

 if (regcomp(&re,

     "^([0-9]{1,3}) *[,;] *([0-9]{1,3}) *[,;] *([0-9]{1,2})$",

     REG_EXTENDED)) {

  printf ("internal error in RE6\n");

  exit(1);

 }

 if (!regexec(&re, value, 4, match, 0)) {

  for (i=1; i<=3; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   m_rgb[i-1]=atoi(buf);

   if (m_rgb[i-1]<0) m_rgb[i-1]=0;

   if (m_rgb[i-1]>255) m_rgb[i-1]=255;

  }

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 // Hexadezimales Format wie in HTML (Internetseiten)

 // Format #rrggbb

 if (regcomp(&re,

     "^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$",

     REG_EXTENDED)) {

  printf ("internal error in RE7\n");

  exit(1);

 }

 if (!regexec(&re, value, 4, match, 0)) {

  for (i=1; i<=3; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   m_rgb[i-1]=strtol(buf, NULL, 16);

  }

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 // Ganzzahlige Prozentangaben (0-100)

 // Format rr%, gg%, bb%

 if (regcomp(&re,

     "^([0-9]{1,3})% *[,;] *([0-9]{1,3})% *[,;] *([0-9]{1,3})%$",

     REG_EXTENDED)) {

  printf ("internal error in RE8\n");

  exit(1);

 }

 if (!regexec(&re, value, 4, match, 0)) {

  for (i=1; i<=3; i++) {

   strncpy(buf, value+match[i].rm_so, match[i].rm_eo-match[i].rm_so);

   buf[match[i].rm_eo-match[i].rm_so]='\0';

   m_rgb[i-1]=atoi(buf);

   if (m_rgb[i-1]<0) m_rgb[i-1]=0;

   if (m_rgb[i-1]>100) m_rgb[i-1]=100;

   m_rgb[i-1]=(int)(m_rgb[i-1]*2.55+.5);

  }

  regfree(&re);

  return TRUE;

 }

 regfree(&re);

 

 printf ("%s: Unbekannte Farbangabe: %s\n", m_name, value);

 return FALSE;

}

 

// alloc wird nach dem Wechsel in den Grafikmodus aufgerufen

// und arbeitet die Kette ab, wobei die Farben eingerichtet werden.

void CDEColor::alloc() {

 for (CDEColor *p=m_firstcolor; p!=NULL; p=p->m_nextcolor)

  (*p->m_var)=GrAllocColor(p->m_rgb[0], p->m_rgb[1], p->m_rgb[2]);

}

6.5       Die Klasse CData

6.5.1     Minimalanforderung für jeden Block

Name

Zeichenfolge

Name dieses Blocks

Jeder Block besitzt ein Datenelement mit der Bezeichnung „Name“. Über diesen Namen kann aus anderen Blöcken auf diesen Block verwiesen werden. Damit können zwischen den Blöcken komplexe Beziehungen erstellt werden.

Da bei Blocklisten Leerzeichen zur Trennung genommen werden können, empfiehlt es sich, den Blocknamen ohne Leerzeichen zu wählen. Ansonsten ist zum Verständnis möglichst kein Name mehrfach zu verwenden, auch nicht bei unterschiedlichen Blocktypen.

6.5.2     CData und die Datenbank

Die Klasse CData kümmert sich um die technischen Aspekte der Datenbank. Von ihr sind alle Klassen abgeleitet, die Daten aus der Datenbank beziehen. Wie Daten im Konstruktor der Klasse angefordert werden, wurde schon bei CDataElement beschrieben.

Diese Klasse ist praktisch ausschließlich technischer Natur, ihr Verständnis noch weniger relevant als das von CDataElement. Eine grobe Erklärung der Funktionsweise ergibt sich aus der Kommentaren.

6.5.3     Quellcode

6.5.3.1          Data.h

// Definition der abstrakten Klasse CData

 

// Bits für die einzelnen hiervon abgeleiteten Klassen.

// Rückgabewerte für die Funktion getType().

// Diese Liefert das Bit der entsprechenden Klasse

// sowie aller Klassen, von der diese abgeleitet ist.

#define DATA_TYPE_INIT 1

#define DATA_TYPE_PHASE 2

#define DATA_TYPE_POINT 4

#define DATA_TYPE_PATH  8

#define DATA_TYPE_MASS 16

#define DATA_TYPE_PLANET 32

 

// Basisklasse für alle Objekte, deren Daten in der Datenbank stehen

class CData {

 public:

  // Standardkonstruktor legt eine Liste aller CData-Objekte an.

  CData();

 

  char *m_name; // Name dieses Datenblocks

 

  // find durchsucht diese Liste anhand eines gegebenen Namens und Typs

  static CData *find (const char *name, unsigned type);

 

  // Von den einzelnen Klassen dann überladen, um den Typ zu identifizieren

  // Der Klassenname geht nämlich beim Compilieren verloren

  // und kann so nicht vom Programm direkt verwendet werden.

  virtual unsigned getType()=0;

 

  // Hauptzweck dieser Klasse: Datenbank auslesen

  static void readFile (const char *fname);

 

 protected:

  // Kann überladen werden, um nach Lesen der Daten zu initialisieren

  virtual boolean init() { ; }

 

  // Führt die link()-Funktion für die Datenelementekette aus

  boolean link();

 

  // Initialisiert die CData-Kette rekursiv

  boolean initChain(unsigned type);

 

  CDataElement *m_element; // Erstes Datenelement dieses Objekts, Kettenbeginn

  static CData *m_head;    // Erstes CData-Objekt, Kettenbeginn

  CData *m_next;           // Nächstes CData-Objekt

};

6.5.3.2          Data.cc

// Implementierung der abstrakten Klasse CData

 

#include "voyager.h"

 

// Statisches Datenelement initialisieren: Kettenbeginn

CData *CData::m_head=NULL;

 

// Standardkonstruktor legt eine Kette aller Objekte an

CData::CData() {

 if (m_head==NULL) m_head=this; // Erstes Kettenelement?

 else { // Sonst ans Listenende eintragen

  CData *p;

  for (p=m_head; p->m_next!=NULL; p=p->m_next);

  p->m_next=this;

 }

 m_element=NULL; // Noch keine Elemente verfügbar

 m_name=NULL;    // Noch kein Name genannt

 m_next=NULL;    // vorläufiges Kettenende

 // Datenelement "name" anfordern

 new CDEString(&m_element, "Name", &m_name);

}

 

// find durchsucht die Kette nach Namen und Typ.

CData *CData::find (const char *name, unsigned type) {

 // Ganze Kette durchsuchen

 for (CData *p=m_head; p!=NULL; p=p->m_next) {

  // Typ und name passt? Dann OK

  if ((p->getType()&type) && p->m_name && !strcasecmp(p->m_name, name)) return p;

 }

 return NULL; // nichts gefunden

}

 

// Hauptaufgabe dieser Klasse: Datenbankdatei auslesen.

// Diese funktion ist sehr technisch, ihr Verständnis nicht so wichtig.

void CData::readFile (const char *fname) {

 FILE *f;

 char buf[MAX_LINE];

 int lnum=0, blockstart=-1;

 CData *block=NULL;

 char *p, *key, *value;

 

 f=fopen(fname, "rt"); // Datei öffnen, Nur-lesen, Textmodus

 if (f==NULL) {

  printf ("Konnte %s nicht \224ffnen\n", fname);

  exit(1);

 }

 while (fgets(buf, MAX_LINE, f)) { // jede Zeile lesen

  lnum++;

  p=strstr(buf, "//"); // Kommentare weglassen

  if (p!=NULL) *p='\0';

  // Führende Leerzeichen weglassen

  for (key=buf; *key!='\0' && isspace(*key); key++);

  if (*key=='\0') continue; // Leerzeilen ignorieren

  if (*key=='[') { // Blockbeginn

   if (block!=NULL) { // Schon ein Block angefangen

    if (block->m_name==NULL) {

     printf ("%s Zeilen %d-%d: Block hat keinen Namen.\n", fname, blockstart, lnum-1);

     exit(1);

    }

    if (block->m_element==NULL || block->m_element->needMoreData()) {

     printf ("%s Zeilen %d-%d: Block %s unvollst\204ndig.\n",

             fname, blockstart, lnum-1, block->m_name);

     exit(1);

    }

   }                   // Block ist vollständig

   blockstart=lnum;    // Zeilennummer merken für eventuelle Fehlermeldungen

   *key++;             // Klammer auf überspringen

   p=rindex(key, ']'); // Klammer zu suchen

   if (p==NULL) {

    printf ("%s Zeile %d: Unvollst\204ndiger Blockanfang.\n", fname, lnum);

    exit(1);

   }

   *p='\0';            // Klammer zu löschen

   while (*(++p)!='\0') if (!isspace(*p)) { // Noch Zeilenrest?

    printf ("%s Zeile %d: Warnung: Erwarte Zeilenende nach ].\n", fname, lnum);

    break;

   }

   // Neuen Block entsprechenden Typs anlegen

   if (!strcasecmp(key, "init")) block=new CInit;

   else if (!strcasecmp(key, "phase")) block=new CPhase;

   else if (!strcasecmp(key, "path")) block=new CPath;

   else if (!strcasecmp(key, "Bahn")) block=new CPath;

   else if (!strcasecmp(key, "sun")) block=new CMass;

   else if (!strcasecmp(key, "Sonne")) block=new CMass;

   else if (!strcasecmp(key, "planet")) block=new CPlanet;

   else {

    printf ("%s Zeile %d: Unbekannter Blocktyp: %s\n", fname, lnum, key);

    exit(1);

   }

   continue; // nächste Zeile weitermachen

  } // Kein Blockanfang => Daten

  if (block==NULL) { // Daten brauchen einen Block

   printf ("%s zeile %d: Kein Block angegeben.\n", fname, lnum);

   exit(1);

  }

  p=index(key, '='); // = suchen

  if (p==NULL) {

   printf ("%s Zeile %d: Fehler in Zeile, erwarte \"Bezeichner=Wert\".\n",

           fname, lnum);

   exit(1);

  }

  *p='\0'; // am = aufspalten

  value=p; // Position merken

  // Leerzeichen links vom = löschen

  for (p--; p>=key && isspace(*p); p--) *p='\0';

  if (p<key) { // Es muss etwas ausser Leerzeichen übrigbleiben

   printf ("%s Zeile %d: Kein Bezeichner gegeben.\n", fname, lnum);

   exit(1);

  }

  // Leerzeichen rechts vom = löschen

  for (value++; *value!='\0' && isspace(*value); value++);

  if (*value=='\0') {

   printf ("%s Zeile %d: Kein Wert gegeben.\n", fname, lnum);

   exit(1);

  }

  // Leerzeichen am Zeilenende (samt Zeilenwechsel) löschen

  for (p=rindex(value,'\0')-1; isspace(*p); p--) *p='\0';

 

  // Versuchen, die Daten an den Block zuzuweisen

  switch (block->m_element->assign(key, value)) {

   case CDE_ASSIGN_NOELEM:

    printf ("%s Zeile %d: Bezeichner unbekannt.\n", fname, lnum);

    exit(1);

   case CDE_ASSIGN_TWICE:

    printf ("%s Zeile %d: Element mehrfach angegeben.\n", fname, lnum);

    exit(1);

   case CDE_ASSIGN_PARSE:

    printf ("%s Zeile %d: Fehlerhaftes Datenformat.\n", fname, lnum);

    exit(1);

  } // Daten erfolgreich zugewiesen

 

 } // nächste Zeile

 

 if (ferror(f)) { // Irgendein Dateilesefehler

  printf ("Fehler beim Lesen von %s nach Zeile %d.", fname, lnum);

  exit(1);

 }

 if (block==NULL) { // Kein einziger Block wurde angelegt

  printf ("%s: Keine Daten gefunden.\n", fname);

  exit(1);

 }

 // letzten Block auf Vollständigkeit prüfen

 if (block->m_name==NULL) {

  printf ("%s Zeilen %d-%d: Block hat keinen Namen.\n", fname, blockstart, lnum);

  exit(1);

 }

 if (block->m_element==NULL || block->m_element->needMoreData()) {

  printf ("%s Zeilen %d-%d: Block %s unvollst\204ndig.\n",

          fname, blockstart, lnum, block->m_name);

  exit(1);

 }

 fclose(f); // Datei schliessen

 

 // Blöcke miteinander verknüpfen

 if (!m_head->link()) {

  printf ("%s: Fehlerhafte Verkn\201pfungen.\n", fname);

  exit(1);

 }

 // Blöcke in der richtigen Reihenfolge initialisieren

 if (!(m_head->initChain(DATA_TYPE_MASS) &&

       m_head->initChain(DATA_TYPE_PATH) &&

       m_head->initChain(DATA_TYPE_PHASE) &&

       m_head->initChain(DATA_TYPE_INIT))) {

  printf ("Fehler beim Initialisieren der Daten.\n");

  exit(1);

 }

 // GESCHAFFT!!

}

 

// Rekursiv alle Links einrichten

boolean CData::link() {

 // Dieses Objekt hat Datenelemente => link für die Datenelemente aufrufen

 if (m_element!=NULL && m_element->link()==FALSE) return FALSE;

 if (m_next==NULL) return TRUE; // Letzter Block => alles OK

 return m_next->link();         // Rest der Blockkette linken

}

 

// Rekursiv Blöcke initialisieren

boolean CData::initChain(unsigned type) {

 // Wenn das der richtige Typ ist, dann initialisieren.

 // Wennn das fehlschlägt: raus!

 // (Blöcke falschen Typs werden übergangen, da bei

 // FALSE && Befehl der Befehl nicht mehr ausgeführt wird.

 if ((getType()&type) && !init()) return FALSE;

 // Wenn Kettenende: alles OK

 if (m_next==NULL) return TRUE;

 // Rest der Kette initialisieren

 return m_next->initChain(type);

}

6.6       Die Klasse CPoint

6.6.1     Einstellungen für die Darstellung

Drawsize

Ganzzahl

Größe des Kreises beim Zeichnen

Color

Farbe

Farbe, in der das Objekt dargestellt wird

Diese beiden optionalen Einstellungen beeinflussen das Erscheinungsbild eines Objekts auf dem Bildschirm.

6.6.2     Darstellung mit der Klasse CPoint

Die Klasse CPoint kümmert sich um Objekte, die eindeutig im Raum zu lokalisieren sind. Hauptaufgaben sind es, ein Interface bereitzustellen zur Positionsermittlung, eine Geschwindigkeitsberechnung zu ermöglichen sowie das Objekt auf dem Bildschirm darzustellen.

6.6.3     Quellcode

6.6.3.1          Point.h

// Definition der abstrakten Klasse CPoint

 

// CPoint für Objekte, die eine klar definierte Position im Raum haben

// und an dieser Position auch gezeichnet werden können

class CPoint : public CData {

 public:

  CPoint(); // Standardkonstruktor für Datenanforderung

 

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_POINT; }

 

  // Position zu einem Zeitpunkt bestimmen

  virtual CVector getPos (mtime t)=0;

 

  // Geschwindigkeit zu einem Zeitpunkt bestimmen

  CVector getSpeed (mtime t);

 

  // Ist das Objekt zu diesem Zeitpunkt überhaupt definiert?

  virtual boolean inTime (mtime t) { return TRUE; }

 

  // Objekt zeichnen

  boolean draw (mtime t, CVector origin, double scale, boolean big=FALSE);

 

 protected:

  long m_drawsize; // Grösse des Kreises beim Zeichnen als Kreis

  GrColor m_color; // Farbe

};

6.6.3.2          Point.cc

// Implementierung der abstrakten Klasse CPoint

 

#include "voyager.h"

 

CPoint::CPoint() {

 // Datenelemente bestellen

 new CDEInteger(&m_element, "Drawsize", &m_drawsize, OPTIONAL);

 new CDEColor(&m_element, "Color", &m_color, 0, 0, 255, OPTIONAL);

 m_drawsize=5; // Standardeinstellung für Drwasize

}

 

CVector CPoint::getSpeed (mtime t) {

 // Ungültiger Zeitpunkt => Nullvektor

 if (!inTime(t-.5) || !inTime(t+.5)) return CVector();

 // v(t)=(r(t+0.5s)-r(t-0.5s))/1s

 return (getPos(t+.5)-getPos(t-.5));

}

 

// t:      Zeitpunkt zum Zeichnen

// origin: Raumkoordinaten, die im Bildschirmmittelpunkt liegen

// scale:  Vergrößerungsfaktor bei der Darstellung in Pixel/m (scale<1)

// big:    Als Punkt (Standardeinstellung) oder als Kreis zeichnen?

boolean CPoint::draw (mtime t, CVector origin, double scale, boolean big) {

 // Objekt derzeit definiert?

 if (!inTime(t)) return FALSE;

 

 // Position berechnen, Ursprung verschieben, skalieren,

 // Ursprung in den Bildschirmmittelpunkt (globale Variable) legen

 CVector r=(getPos(t)-origin)*scale+scrctr;

 

 // Kreis oder Punkt malen, dabei y-Achse nach oben

 if (big) GrFilledCircle ((int)r.x, (int)-r.y, m_drawsize, m_color);

 else GrSetPixel ((int)r.x, (int)-r.y, m_color);

 

 return TRUE;

}

6.7       Die Klasse CMass

6.7.1     Der Block [Sun]

mass

Masse

Masse des Körpers

Der Block Sun beschreibt eine ruhende Masse im Koordinatenursprung. Da die entsprechende Klasse jedoch auch für bewegte Massen, also Planeten, als Basisklasse dienen soll, wurde die Klasse allgemein CMass genannt. Das Datenelement mass gilt also für Planeten gleichfalls.

6.7.2     Die Berechnung der Beschleunigung

Die Berechnung der Beschleunigung ist Hauptaufgabe der Klasse CMass. Dabei wird die Position der Sonde sowie der aktuelle Zeitpunkt angegeben, woraus die aktuelle Position der Masse und damit die auf die Sonde wirkende Beschleunigung berechnet wird.

6.7.3     Quellcode

6.7.3.1          Mass.h

// Definition der Klasse CMass

 

// Klasse für Zentralgestirn sowie Basisklasse für Planeten

class CMass : public CPoint {

 protected:

  double m_m; // Masse aus der Datenbank

 

 public:

  CMass (); // Standardkonstruktor für Datenanforderung

 

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_MASS|DATA_TYPE_POINT; }

 

  // CMass direkt ist immer ein im Koordinatenursprung ruhendes Zentralgestirn.

  // Die abgeleitete Klasse CPlanet implementiert eine eigene Version.

  virtual CVector getPos (mtime t) { return CVector(0, 0, 0); }

 

  // Beschleunigung auf einen Körper berechnen

  CVector calcAcc (mtime t, CVector r);

 

  // Masse liefern (read-only, da m_m protected)

  double getMass() { return m_m; }

};

6.7.3.2          Mass.cc

// Implementierung der Klasse CMass

 

#include "voyager.h"

 

CMass::CMass () {

 new CDEMass(&m_element, "m", &m_m); // Masse zwingend erforderlich

}

 

// Berechnet die Beschleunigung auf einen Körper durch die Gravitation dieses Körpers.

// t: Zeitpunkt, r: Ort des Körpers, der beschleunigt wird

CVector CMass::calcAcc (mtime t, CVector r) {

 // Differenzvektor mit der Position der Masse zum gegebenen Zeitpunkt

 CVector d=getPos(t)-r;

 // Newtons Gravitationsformel (2): a = F/m = r*G*M / |r|³

 CVector a=d*(GRAV*m_m/pow(d.abs(), 3));

 return a;

}

6.8       Die Klasse CPlanet

6.8.1     Der Block [Planet]

Epoch

Zeitpunkt

Zeitpunkt, für den die Daten gelten

T

Zeitspanne

Umlaufperiode des Planeten

a

Entfernung

Große Halbachse der Bahnellipse

e

reelle Zahl

Numerische Exzentrizität der Bahnellipse

i

Winkel

Neigung der Bahnebene gegen die Ekliptik

LAN

Winkel

, Länge des aufsteigenden Knotens

LP

Winkel

, Länge des Perihelpunktes

LM

Winkel

, Länge des Planeten zur Epoche

Precision

Ganzzahl

Anzahl der Skelettpunkte für die Interpolation

6.8.2     Die Berechnung der Skelettdaten

Es werden für die Simulation eine Reihe von Punkten samt zugehörigen Zeiten gespeichert. Die Zeiten liegen dabei im Bereich [0; T] und sind im Bezug auf die Epoche als Nullpunkt zu verstehen. Durch Abzug aller vollständigen Umdrehungen lässt sich zu jeder beliebigen Zeit eine entsprechende Zeit in diesem Intervall finden, zu der der Planet an der gleichen Position steht.

Als Laufvariable für die Berechnung der Punkte wird der in Abschnitt 4.4.2 erklärte Parameter p verwendet. Die einzelnen Schritte der Datenberechnung sind als Kommentare im Programm zu erkennen.

6.8.3     Quellcode

6.8.3.1          Planet.h

// Definition der Klasse CMass

 

class CPlanet : public CMass {

 protected:

  // Daten aus der Datenbank

  mtime m_T, m_epoch; // Umlaufzeit, Epoche der Daten

  double m_a, m_e, m_i, m_LAN, m_LP, m_LM; // Bahnelemente im Längengradformat

 

  // Datenskelett

  struct PlanetPos {

   mtime t;   // Zeit seit der Epoche

   CVector r; // Ortsvektor

  } *m_poslist; // Pointer: Array unbestimmter Größe

  long m_prec;  // m_poslist hat m_prec+1 Einträge

 

 public:

  // Konstruktor und Initialisierung

  CPlanet ();

  virtual boolean init();

 

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_PLANET|DATA_TYPE_MASS|DATA_TYPE_POINT; }

 

  // Eigene Methode zur Positionsberechnung

  virtual CVector getPos (mtime t);

};

6.8.3.2          Planet.cc

// Implementierung der Klasse CMass

 

#include "voyager.h"

 

CPlanet::CPlanet() {

 new CDETime(&m_element, "Epoch", &m_epoch, OPTIONAL);

 new CDETimeSpan(&m_element, "T", &m_T);

 new CDEDistance(&m_element, "a", &m_a);

 new CDEReal(&m_element, "e", &m_e);

 new CDEAngle(&m_element, "i", &m_i);

 new CDEAngle(&m_element, "LAN", &m_LAN);

 new CDEAngle(&m_element, "LP", &m_LP);

 new CDEAngle(&m_element, "LM", &m_LM);

 new CDEInteger(&m_element, "Precision", &m_prec, OPTIONAL);

 m_epoch=0.; // Standardepoche J2000.0

 m_prec=DEF_PREC; // Standardpräzision

}

 

// Initialisieren: Datenskelett anlegen

boolean CPlanet::init() {

 if (!CMass::init()) return FALSE; // Basisklasse initialisieren

 

 // Planeten nur auf Ellipsen

 if (m_e<0. || m_e>=1.) {

  printf ("Planetenbahn von %s muss Ellipse sein\n", m_name);

  return FALSE;

 }

 

 // Präzision muss im richtigen Bereich liegen

 if (m_prec<=0L) m_prec=DEF_PREC;

 if (m_prec<MIN_PREC) m_prec=MIN_PREC;

 if (m_prec>MAX_PREC) m_prec=MAX_PREC;

 

 // Kleine Halbachse berechnen: b=a*sqrt(|1-e²|) (21)

 double b=m_a*sqrt(fabs(1-sqr(m_e)));

 

 // h=2dA/dt=const. (8) über Umlaufdauer berechnen: h=2*PI*a*b/T

 double h=2.*PI*m_a*b/m_T;

 

 // Umwandlung in ekliptikale Elemente (26)

 // Perihelargument aus den Längen

 double o=atan2(sin(m_LP-m_LAN), cos(m_LP-m_LAN)*cos(m_i));

 

 double v=atan2(sin(m_LM-m_LAN), cos(m_LM-m_LAN)*cos(m_i))-o; // wahre Anomalie (26)

 // Parameter p zur Epoche berechnen: (29)

 double pm=atan2(m_a*sin(v)*(1-sqr(m_e)), b*(cos(v)+m_e));

 // Zu pm die Fläche berechnen: (28)

 double am=(pm-sin(pm)*m_e)/2.*m_a*b;

 

 // Speicher für die Skelettdaten belegen

 m_poslist=new PlanetPos[m_prec+1];

 

 for (long i=0; i<=m_prec; i++) {

  double x1, y1, area, t; // x', y', A, t

 

  // p in [-PI; PI], beginnend mit pm

  double p=fmod((i/((double)m_prec)*2.*PI)+PI+pm, 2.*PI)-PI;

 

  // x' und y' sind Kreiskoordinaten

  x1=cos(p);

  y1=sin(p);

 

  // Fläche berechnen (28)

  area=(p-y1*m_e)/2.*m_a*b; // entspricht Zeit seit Perihel

  area-=am; // entspricht Zeit seit Epoche

 

  // Vektor anlegen und im Raum ausrichten

  CVector r=CVector(m_a*(x1-m_e), b*y1).rotz(o).rotx(m_i).rotz(m_LAN);

 

  t=area*2./h; // wegen (8)

  // t in [0; T[

  if (t<0.) t+=m_T;

  if (t>=m_T) t-=m_T;

 

  // Zeit und Ort speichern

  m_poslist[i].r=r;

  m_poslist[i].t=t;

 }

 // Idealisierte Zeiten für die Ränder, damit Interpolation sauber läuft

 m_poslist[0].t=0;

 m_poslist[m_prec].t=m_T;

 return TRUE; // Initialisierung OK

}

 

// Bestimmt den Ort zu einer gegebenen Zeit durch lineare Interpolation

CVector CPlanet::getPos (mtime t) {

 mtime rt=t-m_epoch; // Zeit seit Epoche

 rt-=m_T*floor(rt/m_T); // ganze Umdrehungen abziehen => RoundTime rt

 

 // nächsten vorberechneten Wert finden

 static long i=0; // static, um nicht immer von vorne zu suchen => schneller

 if (i>=m_prec || rt < m_poslist[i].t) i=0; // doch von vorne suchen

 

 // Ersten Zeitpunkt danach finden

 for (i++; i<m_prec && m_poslist[i].t<rt; i++);

 i--; // i (davor) und i+1 (danach) sind die nächsten Werte

 

 // Wert linear interpolieren

 // f: Bruchteil der Zeit zwischen Punkt i und i+1: f=(t-t1)/(t2-t1)

 double f=(rt-m_poslist[i].t)/(m_poslist[i+1].t-m_poslist[i].t);

 // r in entsprechendem Verhältnis: r=r1+f*(r2-r1)

 CVector r=m_poslist[i].r+(m_poslist[i+1].r-m_poslist[i].r)*f;

 

 return r;

}

6.9       Die Klasse CPath

6.9.1     Der Block [Path]

Center

CPoint

Gravitationszentrum der Bahnkurve

Epoch

Zeitpunkt

Zeitpunkt, für den die Daten gelten

a

Entfernung

Große Halbachse der Bahnellipse

e

reelle Zahl

Numerische Exzentrizität der Bahnellipse

i

Winkel

Neigung der Bahnebene gegen die Ekliptik

OM

Winkel

, Länge des aufsteigenden Knotens

o

Winkel

, Argument des Perihels

M

Winkel

, Mittlere Anomalie zur Epoche

System

Zeichenfolge

FK4 oder FK5

Precision

Ganzzahl

Anzahl der Skelettpunkte für die Interpolation

6.9.2     CPath im Unterschied zu CPlanet

Für die Soll-Werte der Bahndaten habe ich eine eigene Klasse eingerichtet, wofür viele Gründe sprechen. Die Bahnen werden nur einmal durchflogen, zu vielen Zeitpunkten ist also gar nichts zu zeichnen. Das Format der entsprechenden Daten ist ganz anders als das der Planeten. Bahnen können auch hyperbolisch sein. Eine Masse ist jedoch nicht erforderlich, weshalb eine Ableitung von CMass sinnlos gewesen wäre.

Trotzdem finden sich natürlich in der Berechnung der Skelettdaten starke Parallelen zu den Berechnungen bei Planeten.

6.9.3     Die Geschwindigkeit im Perihelpunkt

Aus rechentechnischen Gründen wird hier ein besonderes Verfahren verwendet, um die Geschwindigkeit eines Körpers im Perihelpunkt zu berechnen. Dieses Verfahren geht davon aus, dass h bekannt ist. Die Formel lässt sich so begründen:

Im Perihel ist (24):

Die verwendete Formel ist  und mit (23) identisch zu obiger:

 q.e.d.

Der Grund für diese Berechnung ist die kürzere Schreibweise sowie die schnellere Rechenzeit (keine Wurzel nötig).

6.9.4     Quellcode

6.9.4.1          Path.h

// Definition der Klasse CPath

 

class CPath : public CPoint {

 protected:

  CMass *m_center; // Fixpunkt der Bahn

  mtime m_epoch;   // Epoche der Bahndaten

  double m_a, m_e, m_i, m_OM, m_o, m_M; // ekliptikale Elemente

  char *m_sysname; // Koordinatensystem (FK4 oder FK5)

 

  // Datenskelett

  struct PathPos {

   mtime t;       // Zeitpunkt (bezüglich J2000.0)

   CVector r;     // Ortsvektor

  } *m_poslist;

  long m_prec; // m_poslist hat m_prec+1 Einträge

 

  // Zur Initialisierung der Simulationmit den Perihelwerten

  mtime m_T; // Zeitpunkt des Periheldurchgangs

  CVector m_ir, m_iv; // r und v im Perihel

 

 public:

  // Konstruktor und Initialisierung

  CPath ();

  boolean init();

 

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_PATH|DATA_TYPE_POINT; }

 

  // Objekt zu dem Zeitpunkt überhaupt definiert?

  virtual boolean inTime (mtime t)

                    { return (t>=m_poslist[0].t && t<=m_poslist[m_prec].t); }

 

  // Eigene Version der Positionsbestimmung

  virtual CVector getPos (mtime t);

 

  // Initialisierung der Variablen mit den Periheldaten

  void getPerihel(mtime *t, CVector *r, CVector *v);

};

6.9.4.2          Path.cc

// Implementierung von CPath

 

#include "voyager.h"

 

// Konstanten für die Umrechung von FK4 auf FK5 (27)

#define SC_L 4.50001688*DEG

#define SC_J 0.00651966*DEG

#define SC_W 5.19856209*DEG+m_OM

 

CPath::CPath() {

 // Daten anfordern

 new CDELink(&m_element, "Center", DATA_TYPE_MASS, (CData**)&m_center);

 new CDETime(&m_element, "Epoch", &m_epoch);

 new CDEDistance(&m_element, "a", &m_a);

 new CDEReal(&m_element, "e", &m_e);

 new CDEAngle(&m_element, "i", &m_i);

 new CDEAngle(&m_element, "OM", &m_OM);

 new CDEAngle(&m_element, "o", &m_o);

 new CDEAngle(&m_element, "M", &m_M);

 new CDEString(&m_element, "System", &m_sysname, OPTIONAL);

 new CDEInteger(&m_element, "Precision", &m_prec, OPTIONAL);

 // Voreinstellungen für die optionalen Variablen

 m_sysname=NULL; // FK5 verwenden

 m_prec=DEF_PREC;

}

 

boolean CPath::init() {

 if (!CPoint::init()) return FALSE; // Basisklasse initialisieren

 

 // Keine negative Exzentrizität oder Parabel

 if (m_e<0. || m_e==1.) {

  printf ("Unzul\204ssige Exzentrizit\204t bei %s\n", m_name);

  return FALSE;

 }

 

 // Koordinatensystem lesen und ggf. umwandeln

 if (m_sysname==NULL || !strcasecmp(m_sysname, "FK5")); // Daten OK

 else if (!strcasecmp(m_sysname, "FK4")) {

  // Umwandlung (27) nötig

  m_o+=atan2(sin(SC_J)*sin(SC_W), sin(m_i)*cos(SC_J)+cos(m_i)*sin(SC_J)*cos(SC_W));

  double inc=acos(cos(m_i)*cos(SC_J)-sin(m_i)*sin(SC_J)*cos(SC_W));

  m_OM=atan2(sin(m_i)*sin(SC_W), cos(m_i)*sin(SC_J)+sin(m_i)*cos(SC_J)*cos(SC_W))-SC_L;

  m_i=inc;

 }

 else {

  printf ("Koordinatensystem %s unbekannt, erwarte FK4 oder FK5.\n", system);

  return FALSE;

 }

 

 // Präzision muss im richtigen Bereich liegen

 if (m_prec<=0L) m_prec=DEF_PREC;

 if (m_prec<MIN_PREC) m_prec=MIN_PREC;

 if (m_prec>MAX_PREC) m_prec=MAX_PREC;

 

 // Zeitpunkt des Periheldurchgangs berechnen (25): T=t-M*(a³/GM)

 m_T=m_epoch-m_M*sqrt(pow(fabs(m_a),3)/GRAV/m_center->getMass());

 

 // Kleine Halbachse berechnen (21): b=a*sqrt(|1-e²|)

 double b=m_a*sqrt(fabs(1-sqr(m_e)));

 

 // h=2dA/dt=const. berechnen (23): h=sqrt(a(1-e²)GM)

 double h=sqrt(m_a*(1.-sqr(m_e))*GRAV*m_center->getMass());

 

 // r und v beim Start im Perihel zur Initialisierung (Funktion getPerihel)

 m_ir=CVector(m_a*(1-m_e), 0.).rotz(m_o).rotx(m_i).rotz(m_OM)+m_center->getPos(m_T);

 // Im Perihel ist v senkrecht zu r.

 m_iv=CVector(0., h/(m_a*(1-m_e))).rotz(m_o).rotx(m_i).rotz(m_OM)+

              m_center->getSpeed(m_T);

 

 // Speicher für die Skelettdaten belegen

 m_poslist=new PathPos[m_prec+1];

 

 for (long i=0; i<=m_prec; i++) {

  double x1, y1, area; // x', y', A

  if (m_e>1.) { // Hyperbel

   double p=((i+1.)/(m_prec+2.)*PI)-PI/2.; // p in ]-PI/2; PI/2[

  

   // Hyperbel mit a=1, b=1 gibt x' und y'

   x1=1/cos(p);

   y1=tan(p);

  

   // obere Hälfte um 45° nach links und skalieren gibt y'''=1/x'''

   double x2=x1-fabs(y1);

   double y2=x1+fabs(y1);

  

   area=((m_e+y2)*(m_e-x2)-(m_e+1.)*(m_e-1.))/2.+log(x2); // Fläche berechnen

   if (y1<0.) area=-area; // Vorzeichen für den anderen halben Ast berücksichtigen

   area*=m_a*b/2.; // Effekt aller Abbildungen rückgängig

  }

  else { // Ellipse

   double p=(i/((double)m_prec)*2.*PI)-PI; // p in [-PI; PI]

 

   // x' und y' sind Kreiskoordinaten

   x1=cos(p);

   y1=sin(p);

  

   // Fläche berechnen

   area=(p-y1*m_e)/2.*m_a*b;

  }

 

  // Vektor anlegen und im Raum ausrichten

  // Dabei muss der Nullpunkt der x-Achse in den Brennpunkt verschoben werden

  // Durch den Betrag von b wird der andere Umlaufsinn der Hyperbeln realisiert

  CVector r=CVector(m_a*(x1-m_e), fabs(b)*y1).rotz(m_o).rotx(m_i).rotz(m_OM);

 

  // Zeit (aus Fläche) und Ort speichern

  m_poslist[i].t=area*2./h+m_T;

  m_poslist[i].r=r;

 }

 return TRUE; // Initialisierung OK

}

 

CVector CPath::getPos (mtime t) {

 if (!inTime(t)) return CVector();

 

 // nächsten vorberechneten Wert finden

 static long i=0; // static, um nicht immer von vorne zu suchen => schneller

 if (i>=m_prec || t < m_poslist[i].t) i=0; // doch von vorne suchen

 

 // Ersten Zeitpunkt danach finden

 for (i++; i<m_prec && m_poslist[i].t<t; i++) ;

 i--; // i (davor) und i+1 (danach) sind die nächsten Werte

 

 // Wert linear interpolieren

 // f: Bruchteil der Zeit zwischen Punkt i und i+1: f=(t-t1)/(t2-t1)

 double f=(t-m_poslist[i].t)/(m_poslist[i+1].t-m_poslist[i].t);

 // r in entsprechendem Verhältnis: r=r1+f*(r2-r1)

 CVector r=m_poslist[i].r+(m_poslist[i+1].r-m_poslist[i].r)*f;

 

 // Position ist relativ zum Zentralkörper => dessen Position noch dazu

 return (r+m_center->getPos(t));

}

 

void CPath::getPerihel(mtime *t, CVector *r, CVector *v) {

 // Liefert die Daten für den Perihelpunkt dieser Bahn

 *t=m_T;

 *r=m_ir;

 *v=m_iv;

}

6.10   Die Klasse CInit

6.10.1  Der Block [Init]

DispName

Zeichenfolge

Wird in der Auswahl angezeigt

Start

CPhase

Name der ersten Phase

t

Zeitpunkt

: Startzeit der Simulation

rx, ry, rz

Entfernung

: Anfangsposition

vxdt, vydt, vzdt

Entfernung

: Anfangsgeschwindigkeit

dt

Zeitspanne

Zur Bestimmung der bei  verwendeten Einheit

tcorr

Zeitspanne

Ändert die Startzeit

TakeInit

CPoint

Übernimmt die Bahndaten

TimestepBase

Ganzzahl

s. 4.5

Dispstep

Ganzzahl

Anzahl der Iterationen zwischen den Bildschirmaktualisierungen

ColorProbe

Farbe

Farbe zur Darstellung der Sonde

6.10.2  Start der Simulation

Der Init-Block ist eine Art Einstiegspunkt in die Datenbank. Nach Aufruf des Programms zeigt dieses alle Init-Blöcke, in denen der Wert DispName angegeben ist. Durch Tastendruck kann einer dieser Blöcke dann gestartet werden. Dadurch können verschiedene Flüge in einer Datei gespeichert werden.

Doch auch während der Simulation können Init-Blöcke eingebunden werden, um die Flugbahn zu korrigieren. Diese Blöcke müssen nicht benannt sein, d.h. der Wert DispName muss nicht angegeben werden. Das Element name ist natürlich wie bei allen Blöcken nötig, um auf den Block zu verweisen.

Start kennzeichnet die erste Flugphase.

6.10.3  Möglichkeiten zur Angabe der Startwerte

Es gibt zwei grundsätzlich verschiedene Methoden, um Startwerte für die Simulation anzugeben:

1.      Direkte Angabe von ,  und . Die Geschwindigkeit wird dabei als Strecke und Zeit getrennt eingegeben. Das ermöglicht beliebige Kombination der Einheiten, so dass einfache , aber auch  angegeben werden können. dt ist also die zugrundeliegende Zeiteinheit. Für diese Form der Initialisierung müssen also t, rx, ry, rz, vxdt, vydt, vzdt und dt angegeben werden.

2.      Die wesentlich einfachere Form der Initialisierung ist der Start mit den Werten einer bekannten Bahnkurve. Dazu gibt man einfach mit TakeInit den Namen dieser Kurve an. Gibt man noch eine Zeit t an, so wird der entsprechende Punkt der Kurve genommen, sonst der Perihelpunkt. Man kann auch diese Werte als Grundlage nehmen und variieren. Gibt man Komponenten von  oder  an, so werden diese zu den aus der Bahnkurve abgeleiteten addiert. Will man  ändern, so ist dafür die Zeitkorrektur tcorr zu verwenden.

6.10.4  Quellcode

6.10.4.1      Init.h

// Definition der Klasse CInit

 

// Init.h wird vor Phase.h geladen, CPhase ist also hier noch unbekannt.

// Deshalb muss hier definiert werden, DASS es eine Klasse CPhase gibt.

class CPhase;

 

class CInit : public CData {

 public:

  // Konstruktor und Initialisierung

  CInit();

  virtual boolean init();

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_INIT; }

 

  // Gibt eine Liste aller benannten CInit-Objekte aus

  static void list();

 

  // Startwerte für die Simulation

  CVector m_r, m_v;

  mtime m_t;

 

  CPhase *m_start;        // Erste Flugphase

  double m_timestep_base; // s. Erklärung Iterationsverfahren

  long m_dispstep;        // Alle x Iterationen Bildschirmanzeige aktualisieren

  GrColor m_color;        // Farbe für die Sonde

  static CInit *m_init[26]; // Array für alle benannten CInit-Objekte

  static int m_ninits;    // Anzahl der gültigen Elemente in m_init

 

 protected:

  char *m_dispname;       // Anzeigename bei benannten Objekten

 

  // Variablen für diverse Datenelemente

  mtime m_dt, m_tcorr;

  double m_rx, m_ry, m_rz, m_vxdt, m_vydt, m_vzdt;

 

  CPoint *m_take;         // Objekt zum Initialisieren der Bahn

};

6.10.4.2      Init.cc

#include "voyager.h"

 

int CInit::m_ninits=0;

CInit *CInit::m_init[26];

 

CInit::CInit() {

 // Datenelemente anfordern

 new CDEString(&m_element,"DispName", &m_dispname, OPTIONAL);

 new CDELink(&m_element,"Start", DATA_TYPE_PHASE, (CData**)&m_start);

 new CDETime(&m_element,"t", &m_t, OPTIONAL);

 new CDEDistance(&m_element,"rx", &m_rx, OPTIONAL);

 new CDEDistance(&m_element,"ry", &m_ry, OPTIONAL);

 new CDEDistance(&m_element,"rz", &m_rz, OPTIONAL);

 new CDEDistance(&m_element,"vxdt", &m_vxdt, OPTIONAL);

 new CDEDistance(&m_element,"vydt", &m_vydt, OPTIONAL);

 new CDEDistance(&m_element,"vzdt", &m_vzdt, OPTIONAL);

 new CDETimeSpan(&m_element,"dt", &m_dt, OPTIONAL);

 new CDETimeSpan(&m_element,"tcorr", &m_tcorr, OPTIONAL);

 new CDELink(&m_element,"TakeInit", DATA_TYPE_PATH|DATA_TYPE_PLANET,

             (CData**)&m_take, OPTIONAL);

 new CDEReal(&m_element,"TimestepBase", &m_timestep_base, OPTIONAL);

 new CDEInteger(&m_element,"Dispstep", &m_dispstep, OPTIONAL);

 new CDEColor(&m_element,"ColorProbe", &m_color, 255, 0, 0, OPTIONAL);

 // Standardwerte

 m_dispname=NULL;

 m_rx=m_ry=m_rz=m_vxdt=m_vydt=m_vzdt=0.;

 m_tcorr=0.L;

 m_timestep_base=DEF_TIMESTEP_BASE;

 m_dispstep=DEF_DISPSTEP;

}

 

// Initialisiert m_r, m_v und m_t entsprechend den Datenelementen

boolean CInit::init() {

 // Wenn ein Name angegeben, im Array speichern

 if (m_dispname!=NULL) {

  if (m_ninits==26)

   printf ("Warnung: Mehr als 26 Init-Bl\224cke k\224nnen nicht angezeigt werden.\n");

  else m_init[m_ninits]=this;

  m_ninits++;

 }

 

 // Initialisierung über TakeInit

 if ((*m_element)["TakeInit"]->m_havedata) {

  if ((*m_element)["rx"]->m_havedata ||

      (*m_element)["ry"]->m_havedata ||

      (*m_element)["rz"]->m_havedata ||

      (*m_element)["vxdt"]->m_havedata ||

      (*m_element)["vydt"]->m_havedata ||

      (*m_element)["vzdt"]->m_havedata ||

      (*m_element)["dt"]->m_havedata) {

   printf ("Die Angabe von rx, ry, rz, vxdt, vydt, vzdt oder dt\n");

   printf ("ist \201berfl\201ssig, da Perihel angegeben ist.\n");

   return FALSE;

  }

  // Bei Angabe von t ntsprechenden Zeitpunkt nehmen

  if ((*m_element)["t"]->m_havedata) {

   if (!m_take->inTime(m_t)) {

    printf ("%s ist zum in %s angegebenen Zeitpunkt nicht definiert.\n",

            m_take->m_name, m_name);

    return FALSE;

   }

   m_r=m_take->getPos(m_t);

   m_v=m_take->getSpeed(m_t);

  }

  // Sonst aus dem Perihel starten

  else {

   if (!(m_take->getType()&DATA_TYPE_PATH)) {

    printf ("Start aus dem Perihel (ohne Angabe von t)\n");

    printf ("nur mit [Path]-Bl\224cken m\224glich\n");

    return FALSE;

   }

   ((CPath*)m_take)->getPerihel(&m_t, &m_r, &m_v);

  }

 }

 // Keine initialisierung über TakeInit => Einzeldaten erforderlich

 else {

  if (!(*m_element)["t"]->m_havedata ||

      !(*m_element)["rx"]->m_havedata ||

      !(*m_element)["ry"]->m_havedata ||

      !(*m_element)["rz"]->m_havedata ||

      !(*m_element)["vxdt"]->m_havedata ||

      !(*m_element)["vydt"]->m_havedata ||

      !(*m_element)["vzdt"]->m_havedata ||

      !(*m_element)["dt"]->m_havedata) {

   printf ("In Abwesenheit der Angabe TakeInit sind"

           " folgende Daten unbedingt notwendig:\n");

   printf ("t, rx, ry, rz, vxdt, vydt, vzdt, dt\n");

   return FALSE;

  }

  m_r=m_v=CVector(); // Nullvektor, da rx...vz in jedem Fall addiert werden.

 }

 // Fals eine Komponente nicht angegeben wurde, gilt noch der Wert 0

 // aus dem Konstruktor. Erneute Überprüfung daher unnötig.

 m_r+=CVector(m_rx, m_ry, m_rz); // rx, ry, rz addieren

 if ((*m_element)["dt"]->m_havedata) // Ohne dt keine Geschwindigkeiten

  m_v+=CVector(m_vxdt/m_dt, m_vydt/m_dt, m_vzdt/m_dt);

 m_t+=m_tcorr; // tcoor auch in jedem Fall addieren

 return TRUE;

}

 

// Array abarbeiten, jeweils Taste und Namen anzeigen

void CInit::list() {

 for (int i=0; i<m_ninits; i++)

  printf ("%c) %s\n", 'A'+i, m_init[i]->m_dispname);

}

6.11   Die Klasse CPhase

6.11.1  Der Block [Phase]

Zoom

Entfernung

Entfernung, der die Bildschirmhöhe entspricht

Gravity

CMass

Liste der Körper, die bei der Gravitationsberechnung berücksichtigt werden

Draw

CPoint

Liste aller Objekte, die auf dem Bildschirm dargestellt werden

DrawBig

CPoint

Liste aller Objekte, die als Kreise auf dem Bildschirm dargestellt werden

Focus

CPoint

Objekt, das im Bildschirmmittelpunkt liegt

Epoch

Zeit

Zeitpunkt, zu dem das Objekt im Mittelpunkt ist

x, y, z

Entfernung

Mittelpunkt / Mittelpunktsverschiebung

Next

CPhase oder

CInit

Die nächste Phase oder der nächste Init-Block

End

Zeit

Ende dieser Phase

6.11.2  Die Darstellungsperspektive

Die Klasse CPhase kümmert sich um die eigentliche Simulation der Raumsonde und übernimmt außerdem die Darstellung aller darzustellenden Objekte. Der Bildschirm stellt dabei eine Projektion des Raums in die xy-Ebene dar. Der Betrachter blickt so auf das System, dass die x-Achse wie gewohnt nach rechts und die y-Achse nach oben verläuft.

Zur Angabe, welcher Raumpunkt im Bildschirmmittelpunkt liegen soll, gibt es wieder zwei Methoden. Entweder direkt über x, y und z oder über Angabe eines CPoint-Objekts samt Zeitpunkt, wobei x, y und z wieder zur Nachkorrektur benutzt werden können.

Das Datenelement Zoom gibt an, welcher Maßstab bei der Darstellung verwendet wird. Die angegebene Entfernung entspricht der minimalen Distanz vom Bildschirmmittelpunkt zum Bildschirmrand, also der Entfernung vom Bildschirmmittelpunkt zur oberen oder unteren Kante oder der halben Bildschirmhöhe.

6.11.3  Das Ende einer Flugphase

Es gibt grundsätzlich zwei Möglichkeiten, wie das Ende einer Flugphase festgelegt ist. Die einfache ist die Angabe eines konkreten Zeitpunkts End, zu dem die Phase beendet wird.

Wird kein derartiges Ende angegeben, endet die Phase, sobald die Sonde den Einflussbereich des ersten mit Gravity angegebenen Himmelskörpers verlässt. Dies ist der Fall, wenn die von einem anderen Himmelskörper bewirkte Beschleunigung der Sonde größer ist als die durch diesen ersten Körper bewirkte.

Nach dem Ende wird der mit Next angegebene Block ausgeführt. Dabei kann es sich um eine weitere Phase handeln oder um eine neue Initialisierung mit einem Init-Block.

6.11.4  Quellcode

6.11.4.1      Phase.h

// Definition der Klasse CPhase

 

class CPhase : public CData {

 public:

  // Konstruktor und Initialisierung

  CPhase ();

  virtual boolean init();

  // Typerkennung

  virtual unsigned getType() { return DATA_TYPE_PHASE; }

 

  // Hauptfunktion der Simulation

  CData *run (mtime &t, CVector &r, CVector &v, CInit *settings);

 

 protected:

  int fmttime (char *buf, mtime t); // Wandelt mtime in eine Datumsangabe um

 

  // Datenelemente

  CMass **m_mass;               // Gravity

  int m_nmasses;                // Anzahl der Elemente von Gravity

  CPoint **m_point;             // Draw

  int m_npoints;                // Anzahl der Elemente von Draw

  CPoint **m_bigpoint;          // Drwabig

  int m_nbigpoints;             // Anzahl der Elemente von DrawBig

  double m_zoom, m_x, m_y, m_z; // Mittelpunktsverschiebung und Maßstab

  CPoint *m_focus;              // Objekt im Bildschirmmittelpunkt

  mtime m_epoch;                // Epoche

  mtime m_end;                  // Ende dieser Phase

 

  CVector m_center;             // Raumkoordinaten des Bildschirmmittelpunktes

  boolean m_timeend;            // Wurde End angegeben?

  CData *m_nextphase;           // Nächster Block (Phase oder Init)

};

6.11.4.2      Phase.cc

// Implementierung der Klasse CPhase

 

#include "voyager.h"

 

CPhase::CPhase() {

 // Datenbankelemente anfordern

 new CDEDistance(&m_element, "Zoom", &m_zoom);

 new CDELinkList(&m_element, "Gravity", DATA_TYPE_MASS, (CData***)&m_mass);

 new CDELinkList(&m_element, "Draw", DATA_TYPE_POINT, (CData***)&m_point, OPTIONAL);

 new CDELinkList(&m_element, "DrawBig", DATA_TYPE_POINT, (CData***)&m_bigpoint,

                 OPTIONAL);

 new CDELink(&m_element, "Focus", DATA_TYPE_POINT, (CData**)&m_focus, OPTIONAL);

 new CDETime(&m_element, "Epoch", &m_epoch, OPTIONAL);

 new CDEDistance(&m_element, "x", &m_x, OPTIONAL);

 new CDEDistance(&m_element, "y", &m_y, OPTIONAL);

 new CDEDistance(&m_element, "z", &m_z, OPTIONAL);

 new CDELink(&m_element, "Next", DATA_TYPE_PHASE|DATA_TYPE_INIT, (CData**)&m_nextphase,

             OPTIONAL);

 new CDETime(&m_element, "End", &m_end, OPTIONAL);

 m_x=m_y=m_z;

}

 

boolean CPhase::init() {

 // Anzahl der Links speichern

 m_nmasses=((CDELinkList*)((*m_element)["Gravity"]))->length();

 m_npoints=((CDELinkList*)((*m_element)["Draw"]))->length();

 m_nbigpoints=((CDELinkList*)((*m_element)["DrawBig"]))->length();

 

 // Initialisierung über Objekt und Zeit

 if ((*m_element)["Focus"]->m_havedata && (*m_element)["Epoch"]->m_havedata) {

  m_center=m_focus->getPos(m_epoch);

 }

 else if ((*m_element)["Focus"]->m_havedata || (*m_element)["Epoch"]->m_havedata) {

  printf ("%s: Focus und Epoch m\232ssen zusammen angegeben werden\n", m_name);

  return FALSE;

 }

 else m_center=CVector(); // Sonne im Zentrum

 

 m_center+=CVector(m_x, m_y, m_z); // Komponenten addieren

 

 // Speichern, ob ein Endzeitpunkt angegeben wurde

 if ((*m_element)["End"]->m_havedata) m_timeend=TRUE;

 else m_timeend=FALSE;

 

 return TRUE;

}

 

// Hier findet die eigentliche Simulation statt

CData *CPhase::run(mtime &t, CVector &r, CVector &v, CInit *settings) {

 // Eine Variable zählt die Anzahl der Iterationen seit der letzten Aktualisierung der

 // Bildschirmdarstellung. nds steht dabei für non drawing steps.

 int nds=0;

 boolean pause=TRUE; // Jede Phase beginnt im Pause-Modus

 static char key=0;  // Gedrückte Taste wird hier gespeichert.

 char buf[32];       // Buffer für Datumsausgabe

 GrTextOption txo;   // Datentyp für die Formatierung der Textausgabe im Grafikmodus

 

 GrClearScreen (GrBlack());    // Jede Phase löscht den Bildschirm

 memset(&txo, 0, sizeof(txo)); // Rücksetzen = Alle Bytes auf 0 setzen

 txo.txo_font=&GrDefaultFont;  // Standardschriftart => keine Schriftdatei nötig

 txo.txo_fgcolor.v=GrWhite();  // Weiße Schrift...

 txo.txo_bgcolor.v=GrBlack();  // ... auf schwarzem Grund

 

 double scale=GrScreenY()/m_zoom/2.;   // Pixel pro Meter

 

 // Variablen des Init-Blocks lokal speichern

 // für kürzere Schreibweise und schnelleren Zugriff

 double tsb=settings->m_timestep_base;

 int dispstep=settings->m_dispstep;

 GrColor color=settings->m_color;

 

 // DIE SIMULATIONSSCHLEIFE

 do {

  // für ersten Körper Anziehungskraft bzw. Beschleunigung berechnen

  CVector a=m_mass[0]->calcAcc(t, r);

  double ac=a.abs(); // Betrag davon berechnen

 

  // Für alle anderen Körper Beschleunigung berechnen

  for (int i=m_nmasses-1; i>0; i--) {

   CVector a1=m_mass[i]->calcAcc(t, r); // Beschleunigung durch diesen einen Planeten

  

   // Zweite Version des Phasenendes: die Kraft durch diesen Planeten ist stärker als

   // die durch den ersten Planeten der Liste.

   if (!m_timeend && a1.abs() > ac) {

    fmttime(buf,t);      // Uhrzeit formatieren und...

    GrDrawString(buf, strlen(buf), 0, 0, &txo); // ... anzeigen

    if (!(key=getch())) getch(); // Pause bis Tastendruck

    if (key!=27) key=0;          // ESC evtl. merken, sonst Taste ignorieren

    return m_nextphase;          // Info an das Hauptprogramm, wie es weitergeht

   }

 

   a+=a1;                        // Beschleunigungen addieren

  }

  double dt=tsb/a.abs();         // Zeitschritte abhängig von Kraft (siehe 4.5)

 

  // Erste Version des Phasenendes: Angegebener Zeitpunkt erreicht

  if (m_timeend && t+dt>=m_end) {

   dt=m_end-t; // Noch die letzten Sekunden bis zum Phasenende simulieren

   r+=v*dt;

   v+=a*dt;

   t=m_end;

   fmttime(buf,t);      // Uhrzeit formatieren und...

   GrDrawString(buf, strlen(buf), 0, 0, &txo); // ... anzeigen

   if (!(key=getch())) getch(); // Pause bis Tastendruck

   if (key!=27) key=0;          // ESC evtl. merken, sonst Taste ignorieren

   return m_nextphase;          // Info an das Hauptprogramm, wie es weitergeht

  }

 

  // Normale Simulation mit berechneter Zeitdifferenz dt und Beschleunigunbg a

  r+=v*dt;

  v+=a*dt;

  t+=dt;

 

  if (++nds >= dispstep) { // Bildschirmaktualisierung nötig

   // Alle Objekte in entsprechender Größe zeichnen

   for (int i=m_npoints-1; i>=0; i--) m_point[i]->draw(t, m_center, scale, FALSE);

   for (int i=m_nbigpoints-1; i>=0; i--) m_bigpoint[i]->draw(t, m_center, scale, TRUE);

 

   // Anzeigeposition der Sonde ermitteln (analog zu CPoint)

   CVector rd=(r-m_center)*scale+scrctr;

   GrSetPixel ((int)rd.x, (int)-rd.y, color); // Mit der Farbe aus CInit anzeigen

 

   nds=0;                // "blinde" Rechenschritte von vorne zählen

  

   if (pause) {          // Derzeit Pause-Modus

    fmttime(buf,t);      // Uhrzeit formatieren und...

    GrDrawString(buf, strlen(buf), 0, 0, &txo); // ... ausgeben

    if (!(key=getch())) getch(); // Auf Tastendruck warten

   

    // Uhrzeit durch Ausgabe entsprechender Leerzeichen löschen

    memset(buf, ' ', strlen(buf));

    GrDrawString(buf, strlen(buf), 0, 0, &txo);

   }

  } // Ender der Anzeigefunktion

  

  // Wenn Taste gedrückt (entweder in der Pause abgefragt oder noch unbekannt)

  if (key || kbhit()) {

   if (!key) key=getch();           // Taste abfragen

   if (!key && kbhit()) getch();    // Bei Sonderzeichen zweite Taste abfragen

   else if (key==' ') pause=!pause; // Leertaste schaltet Pausemodus um

   if (key!=27) key=0;              // Alle tasten bis auf ESC ignorieren

  }

 } while (!key); // Solange nicht ESC gedrückt wurde Schleife ausführen

 exit(0);        // ESC gedrückt => Programmabbruch

}

 

// Funktion, die mtime als normale Datums- und zeitangabe darstellt

// Verwendet den von Meeus auf Seite 76f. dargestellten Algorithmus

// zur Berechnung des Kalenderdatums aus dem julianischen Datum

int CPhase::fmttime (char *buf, mtime t) {

 int second, minute, hour, day, month, year;

 double a, b, c, d, e;

 long tod=(long)fmod(t-43200., 86400.); // time of day in Sekunden ab 0:00

 if (tod<0L) tod+=86400;                // Für Zeiten vor J2000.0 ist zunächst tod<0

 

 // Integerarithmetik schneidet Nachkommastellen automatisch weg.

 // % ist der Modulo-Operator von C und liefert den Divisionsrest

 // analog zu fmod, jedoch für Ganzzahlen.

 second=tod%60;

 minute=(tod/60)%60;

 hour=tod/3600;

 

 a=t/86400.+2451545.5;             // Zu julianischem Datum umwandeln

 a=a-fmod(a, 1.);                  // a=Z

 b=floor((a-1867216.25)/36524.25); // b=alpha

 

 a=a+1.+b-floor(b/4.);

 b=a+1524.;

 c=floor((b-122.1)/365.25);

 d=floor(365.25*c);

 e=floor((b-d)/30.6001);

 

 day=(int)(b-d-floor(30.6001*e)+.5);

 month=((int)(e-1.5))%12+1;

 year=((int)(c+.5))-(month>2 ? 4716 : 4715);

 

 // Speichern der Werte in buf

 if (year>=0 && year<=9999)

  return sprintf (buf, "%02d.%02d.%04d %02d:%02d:%02d ",

                  day, month, year, hour, minute, second);

 else

  return sprintf (buf, "TIME DISPLAY OVERFLOW");

}

6.12   Das Hauptprogramm

6.12.1  Start des Programms

Da die bisher definierten Objekte fast alle Arbeit entsprechend ihrer Zuständigkeitsbereiche übernehmen, bleiben für das Hauptprogramm kaum noch Aufgaben übrig.

Das Programm verarbeitet die in der Befehlszeile beim Programmstart angegebenen Parameter. Der erste Parameter wird, soweit vorhanden, als Name der Datenbankdatei aufgefasst. Ein zweiter Parameter kann einen Init-Block innerhalb der Datenbankdatei angeben. Dabei muss das Element Name angegeben werden, nicht DispName.

Wird kein Init-Block angegeben, werden die in der Datenbank enthaltenen Blöcke mit ihrem DispName aufgeführt. Der Benutzer kann durch Tastendruck einen davon wählen, um die entsprechende Simulation in Gang zu setzen.

Vor Beginn der Simulation erfolgt der Wechsel in den Grafikmodus.

6.12.2  Koordination der Blöcke

Die Koordination der einzelnen Init- und Phase-Blöcke erfolgt im Hauptprogramm durch zwei ineinander geschachtelte Schleifen. Die äußere ist für die Ausführung der Init-Blöcke zuständig, die innere für alle einem Init-Block folgenden Phase-Blöcke bis zum nächsten Init-Block oder zum Ende der Simulation.

6.12.3  voyager.cc

// Das Hauptprogramm

 

#include "voyager.h"

 

// Globale Variablen definieren

// vorherige Definition in voyager.h war extern, also eigentlich nur ein Verweis auf

// die Definition hier.

CVector scrctr;

 

// Hier beginnt das Programm

int main(int argc, char **argv) {

 // t, r(t) und v(t), die Simulationsvariablen

 mtime t;

 CVector r, v;

 

 CInit *init=NULL; // Der aktuell gültige Init-Block

 

 // Information für den Benutzer

 printf ("Facharbeit Martin von Gagern <martin.vgagern@gmx.net> 1999/2000\n");

 printf ("Syntax: VOYAGER [Datenbank [Initblock]]\n\n");

 

 // Datenbankdatei einlesen

 // Ohne Kommandozeilenparameter: voyager.txt als Datenbank verwenden

 if (argc==1) CData::readFile("voyager.txt");

 // Mit mindestens einem Parameter: Ersten Parameter als Dateiname auffassen

 else CData::readFile(argv[1]);

 

 // Bei zwei Parametern: Entsprechenden Init-Block finden

 // es wird dabei Name gesucht, nicht DispName.

 if (argc==3) {

  init=(CInit*)(CData::find(argv[2], DATA_TYPE_INIT));

 }

 

 // Kein Init-Block angegeben oder Block nicht gefunden

 if (init==NULL) {

  if (CInit::m_ninits==0) { // Gibt es Init-Blöcke mit DispName?

   printf ("Datenbank enth\204lt keine g\201ltigen Init-Bl\224cke.\n");

   exit(1);

  }

  CInit::list();     // Alle Disp-Names auflisten

  int i=-1;          // Bedeutet: kein Block ausgewählt

  do {

   char key=getch(); // auf Tastendruck warten und Taste einlesen

 

   // gültige Buchstabentasten in Arrayindex umwandeln

   if (key>='a' && key<'a'+CInit::m_ninits) i=key-'a';

   else if (key>='A' && key<'A'+CInit::m_ninits) i=key-'A';

  

   else if (key==27) exit (0); // Auswahl und Programm abbrechen

   else printf ("\a");         // Signalton bei ungültiger Taste

  } while (i<0);               // Widerholen solange keine gültige Taste gedrückt wurde

  init=CInit::m_init[i];       // Entsprechenden Init-Block zum aktuellen machen

 }

 

 // Grafikmodus initialisieren

 GrSetDriver("VGA"); // Könnte den Bedingungen entsprechend geändert werden

 GrSetMode(GR_biggest_graphics); // Ganz allgemeine Grafikmodusangabe

 scrctr=CVector(GrScreenX(), -GrScreenY()) /2; // Bildschirmmittelpunkt

 CDEColor::alloc(); // Alle Farben anlegen

 

 // Schleife, die die Init-Blöcke der Reihe nach durcharbeitet

 do {

  // Variablen entsprechend initialisieren

  t=init->m_t;

  r=init->m_r;

  v=init->m_v;

 

  CData *next=init->m_start; // Mit dieser Phase anfangen

 

  // Schleife für alle Phase-Blöcke

  do {

   CPhase *p=(CPhase*)next; // Typkonvertierung zu einem Pointer auf CPhase

   next=p->run(t, r, v, init); // Phase laufen lassen ergibt den nächsten Block

  } while (next!=NULL && (next->getType()&DATA_TYPE_PHASE));

 

  // Die Next-Angabe der letzten Phase ist entweder nicht vorhanden(next=NULL)

  // oder verweist auf einen Init-Block, da die Schleife beendet wurde.

  init=(CInit*)next;

 } while (init!=NULL); // Solange ein weiterer Block kommt

 exit(0); // Normales Programmende

}

6.13   Die Datenbank

Nun muss das Programm natürlich noch mit Daten für die Simulation versorgt werden. Dies erfolgt mit der Datei voyager.txt.

///////////////////////////////////////////////////////////////////////////////

// Datenbank zur Facharbeit: Martin von Gagern <martin.vgagern@gmx.net>

 

///////////////////////////////////////////////////////////////////////////////

// Voyager I informativ

 

[Init]

Name=I1i_EJ

DispName=Voyager I informativ

TakeInit=V1_ErdeJupiter

t=6.9.1977 0:00:00

Start=P1i_EJ

 

[Phase]

Name=P1i_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter

Zoom=5.5 AE

End=15.2.1979 0:00:00

Next=I1i_J

 

[Init]

Name=I1i_J

TakeInit=V1_Jupiter

t=15.2.1979 0:00:00

Start=P1i_J1

 

[Phase]

Name=P1i_J1

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter, V1_JupiterSaturn

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.4 AE

End=1.3.1979 0:00:00

Next=P1i_J2

 

[Phase]

Name=P1i_J2

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter, V1_JupiterSaturn

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.02 AE

End=8.3.1979 0:00:00

Next=P1i_J3

 

[Phase]

Name=P1i_J3

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter, V1_JupiterSaturn

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.4 AE

End=1.4.1979 0:00:00

Next=I1i_JS

 

[Init]

Name=I1i_JS

TakeInit=V1_JupiterSaturn

t=1.4.1979 0:00:00

Start=P1i_JS

 

[Phase]

Name=P1i_JS

Gravity=Sonne, Jupiter, Saturn, Uranus

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus

Draw=V1_Jupiter, V1_JupiterSaturn, V1_Saturn

Zoom=7 AE

x=-5 AE

End=1.10.1980 0:00:00

Next=I1i_S

 

[Init]

Name=I1i_S

TakeInit=V1_Saturn

t=1.10.1980 0:00:00

Start=P1i_S1

 

[Phase]

Name=P1i_S1

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Draw=V1_JupiterSaturn, V1_Saturn, V1_PostSaturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=1 AE

End=10.11.1980 0:00:00

Next=P1i_S2

 

[Phase]

Name=P1i_S2

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Draw=V1_JupiterSaturn, V1_Saturn, V1_PostSaturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=.02 AE

End=20.11.1980 0:00:00

Next=P1i_S3

 

[Phase]

Name=P1i_S3

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Draw=V1_JupiterSaturn, V1_Saturn, V1_PostSaturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=1 AE

End=24.12.1980 0:00:00

Next=I1i_PS

 

[Init]

Name=I1i_PS

TakeInit=V1_PostSaturn

t=24.12.1980 0:00:00

Start=P1i_PS

 

[Phase]

Name=P1i_PS

Gravity=Sonne, Jupiter, Saturn, Uranus

Draw=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=V1_Saturn, V1_PostSaturn

Zoom=30 AE

y=-15 AE

End=1.2.2000 9:00:00 // 9:00 GMT = 10:00 MEZ => Abgabetermin!

 

///////////////////////////////////////////////////////////////////////////////

// Voyager II informativ

 

[Init]

Name=I2i_EJ

DispName=Voyager II informativ

TakeInit=V2_ErdeJupiter

t=21.8.1977 0:00:00

Start=P2i_EJ

 

[Phase]

Name=P2i_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter

Zoom=5.5 AE

End=1.6.1979 0:00:00

Next=I2i_J

 

[Init]

Name=I2i_J

TakeInit=V2_Jupiter

t=1.6.1979 0:00:00

Start=P2i_J1

 

[Phase]

Name=P2i_J1

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter, V2_JupiterSaturn

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.4 AE

End=5.7.1979 0:00:00

Next=P2i_J2

 

[Phase]

Name=P2i_J2

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter, V2_JupiterSaturn

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.02 AE

End=12.7.1979 0:00:00

Next=P2i_J3

 

[Phase]

Name=P2i_J3

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter, V2_JupiterSaturn

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.4 AE

End=15.8.1979 0:00:00

Next=I2i_JS

 

[Init]

Name=I2i_JS

TakeInit=V2_JupiterSaturn

t=15.8.1979 0:00:00

Start=P2i_JS

 

[Phase]

Name=P2i_JS

Gravity=Sonne, Jupiter, Saturn, Uranus

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus

Draw=V2_Jupiter, V2_JupiterSaturn, V2_Saturn

Zoom=7 AE

x=-5 AE

End=20.8.1981 0:00:00

Next=I2i_S

 

[Init]

Name=I2i_S

TakeInit=V2_Saturn

t=20.8.1981 0:00:00

Start=P2i_S

 

[Phase]

Name=P2i_S

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Draw=V2_JupiterSaturn, V2_Saturn, V2_SaturnUranus

Focus=Saturn

Epoch=8/26/1981 03:24:57

Zoom=.02 AE

End=1.9.1981 0:00:00

Next=P2i_SU

 

[Init]

Name=I2i_SU

TakeInit=V2_SaturnUranus

t=10.10.1981 0:00:00

Start=P2i_SU

 

[Phase]

Name=P2i_SU

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=V2_Saturn, V2_SaturnUranus, V2_Uranus

Zoom=12 AE

x=-5 AE

y=-8 AE

End=1.1.1986 0:00:00

Next=I2i_U

 

[Init]

Name=I2i_U

TakeInit=V2_Uranus

t=1.1.1986 0:00:00

Start=P2i_U

 

[Phase]

Name=P2i_U

Gravity=Uranus, Sonne, Jupiter, Saturn, Neptun

DrawBig=Uranus

Draw=V2_SaturnUranus, V2_Uranus, V2_UranusNeptun

Focus=Uranus

Epoch=1/24/1986 17:59:47

Zoom=.02 AE

End=1.2.1986 0:00:00

Next=I2i_UN

 

[Init]

Name=I2i_UN

TakeInit=V2_UranusNeptun

t=1.3.1986 0:00:00

Start=P2i_UN

 

[Phase]

Name=P2i_UN

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun, Pluto

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=V2_Uranus, V2_UranusNeptun, V2_Neptun

Zoom=18 AE

y=-15 AE

End=20.8.1989 0:00:00

Next=I2i_N

 

[Init]

Name=I2i_N

TakeInit=V2_Neptun

t=20.8.1989 0:00:00

Start=P2i_N

 

[Phase]

Name=P2i_N

Gravity=Neptun, Sonne, Jupiter, Saturn, Uranus, Pluto

DrawBig=Neptun

Draw=V2_UranusNeptun, V2_Neptun, V2_PostNeptun

Focus=Neptun

Epoch=8/25/1989 03:56:36

Zoom=.02 AE

End=1.9.1989 0:00:00

Next=P2i_PN

 

[Init]

Name=I2i_PN

TakeInit=V2_PostNeptun

t=1.9.1989 0:00:00

Start=P2i_PN

 

[Phase]

Name=P2i_PN

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=V2_Neptun, V2_PostNeptun

Zoom=30 AE

y=-15 AE

End=1.2.2000 9:00:00 // 9:00 GMT = 10:00 MEZ => Abgabetermin!

 

///////////////////////////////////////////////////////////////////////////////

// Voyager I kosmetisch

 

[Init]

Name=I1k_EJ

DispName=Voyager I kosmetisch

TakeInit=V1_ErdeJupiter

t=6.9.1977 0:00:00

Start=P1k_EJ

 

[Phase]

Name=P1k_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Zoom=5.5 AE

End=15.2.1979 0:00:00

Next=I1k_J

 

[Init]

Name=I1k_J

TakeInit=V1_Jupiter

t=15.2.1979 0:00:00

Start=P1k_J1

 

[Phase]

Name=P1k_J1

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.4 AE

End=1.3.1979 0:00:00

Next=P1k_J2

 

[Phase]

Name=P1k_J2

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.02 AE

End=8.3.1979 0:00:00

Next=P1k_J3

 

[Phase]

Name=P1k_J3

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.4 AE

End=1.4.1979 0:00:00

Next=I1k_JS

 

[Init]

Name=I1k_JS

TakeInit=V1_JupiterSaturn

t=1.4.1979 0:00:00

Start=P1k_JS

 

[Phase]

Name=P1k_JS

Gravity=Sonne, Jupiter, Saturn, Uranus

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus

Zoom=7 AE

x=-5 AE

End=1.10.1980 0:00:00

Next=I1k_S

 

[Init]

Name=I1k_S

TakeInit=V1_Saturn

t=1.10.1980 0:00:00

Start=P1k_S1

 

[Phase]

Name=P1k_S1

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=1 AE

End=10.11.1980 0:00:00

Next=P1k_S2

 

[Phase]

Name=P1k_S2

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=.02 AE

End=20.11.1980 0:00:00

Next=P1k_S3

 

[Phase]

Name=P1k_S3

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Focus=Saturn

Epoch=11/12/1980 23:46:30

Zoom=1 AE

End=24.12.1980 0:00:00

Next=I1k_PS

 

[Init]

Name=I1k_PS

TakeInit=V1_PostSaturn

t=24.12.1980 0:00:00

Start=P1k_PS

 

[Phase]

Name=P1k_PS

Gravity=Sonne, Jupiter, Saturn, Uranus

Draw=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus, Neptun, Pluto

Zoom=30 AE

y=-15 AE

End=1.2.2000 9:00:00 // 9:00 GMT = 10:00 MEZ => Abgabetermin!

 

///////////////////////////////////////////////////////////////////////////////

// Voyager II kosmetisch

 

[Init]

Name=I2k_EJ

DispName=Voyager II kosmetisch

TakeInit=V2_ErdeJupiter

t=21.8.1977 0:00:00

Start=P2k_EJ

 

[Phase]

Name=P2k_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Zoom=5.5 AE

End=1.6.1979 0:00:00

Next=I2k_J

 

[Init]

Name=I2k_J

TakeInit=V2_Jupiter

t=1.6.1979 0:00:00

Start=P2k_J1

 

[Phase]

Name=P2k_J1

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.4 AE

End=5.7.1979 0:00:00

Next=P2k_J2

 

[Phase]

Name=P2k_J2

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.02 AE

End=12.7.1979 0:00:00

Next=P2k_J3

 

[Phase]

Name=P2k_J3

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.4 AE

End=15.8.1979 0:00:00

Next=I2k_JS

 

[Init]

Name=I2k_JS

TakeInit=V2_JupiterSaturn

t=15.8.1979 0:00:00

Start=P2k_JS

 

[Phase]

Name=P2k_JS

Gravity=Sonne, Jupiter, Saturn, Uranus

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus

Zoom=7 AE

x=-5 AE

End=20.8.1981 0:00:00

Next=I2k_S

 

[Init]

Name=I2k_S

TakeInit=V2_Saturn

t=20.8.1981 0:00:00

Start=P2k_S

 

[Phase]

Name=P2k_S

Gravity=Saturn, Sonne, Jupiter, Uranus

DrawBig=Saturn

Focus=Saturn

Epoch=8/26/1981 03:24:57

Zoom=.02 AE

End=1.9.1981 0:00:00

Next=P2k_SU

 

[Init]

Name=I2k_SU

TakeInit=V2_SaturnUranus

t=10.10.1981 0:00:00

Start=P2k_SU

 

[Phase]

Name=P2k_SU

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus, Neptun, Pluto

Zoom=12 AE

x=-5 AE

y=-8 AE

End=1.1.1986 0:00:00

Next=I2k_U

 

[Init]

Name=I2k_U

TakeInit=V2_Uranus

t=1.1.1986 0:00:00

Start=P2k_U

 

[Phase]

Name=P2k_U

Gravity=Uranus, Sonne, Jupiter, Saturn, Neptun

DrawBig=Uranus

Focus=Uranus

Epoch=1/24/1986 17:59:47

Zoom=.02 AE

End=1.2.1986 0:00:00

Next=I2k_UN

 

[Init]

Name=I2k_UN

TakeInit=V2_UranusNeptun

t=1.3.1986 0:00:00

Start=P2k_UN

 

[Phase]

Name=P2k_UN

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun, Pluto

DrawBig=Sonne

Draw=Erde, Jupiter, Saturn, Uranus, Neptun, Pluto

Zoom=18 AE

y=-15 AE

End=20.8.1989 0:00:00

Next=I2k_N

 

[Init]

Name=I2k_N

TakeInit=V2_Neptun

t=20.8.1989 0:00:00

Start=P2k_N

 

[Phase]

Name=P2k_N

Gravity=Neptun, Sonne, Jupiter, Saturn, Uranus, Pluto

DrawBig=Neptun

Focus=Neptun

Epoch=8/25/1989 03:56:36

Zoom=.02 AE

End=1.9.1989 0:00:00

Next=P2k_PN

 

[Init]

Name=I2k_PN

TakeInit=V2_PostNeptun

t=1.9.1989 0:00:00

Start=P2k_PN

 

[Phase]

Name=P2k_PN

Gravity=Sonne, Jupiter, Saturn, Uranus, Neptun, Pluto

Draw=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus, Neptun, Pluto

Zoom=30 AE

y=-15 AE

End=1.2.2000 9:00:00 // 9:00 GMT = 10:00 MEZ => Abgabetermin!

 

///////////////////////////////////////////////////////////////////////////////

// Ansicht für ehrliche Simulation

 

[Phase]

Name=P_E1

Gravity=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus

Zoom=15 AE

x=-5 AE

End=1.1.1986 0:00:00

Next=P_E2

 

[Phase]

Name=P_E2

Gravity=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus

Draw=Sonne, Merkur, Venus, Erde, Mars, Jupiter, Saturn, Uranus, Neptun, Pluto

Zoom=30 AE

x=-15 AE

End=1.2.2000 9:00:00 // 9:00 GMT = 10:00 MEZ => Abgabetermin!

 

///////////////////////////////////////////////////////////////////////////////

// Voyager I ehrlich

 

[Init]

Name=I1e_EJ

DispName=Voyager I ehrlich

TakeInit=V1_ErdeJupiter

t=6.9.1977 0:00:00

Start=P1e_EJ

 

[Phase]

Name=P1e_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter

Zoom=5.5 AE

End=15.2.1979 0:00:00

Next=P1e_J

 

[Phase]

Name=P1e_J

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V1_ErdeJupiter, V1_Jupiter, V1_JupiterSaturn

Focus=Jupiter

Epoch=3/5/1979 12:05:26

Zoom=.4 AE

Next=P_E1

 

///////////////////////////////////////////////////////////////////////////////

// Voyager II ehrlich

 

[Init]

Name=I2e_EJ

DispName=Voyager II informativ

TakeInit=V2_ErdeJupiter

t=21.8.1977 0:00:00

Start=P2e_EJ

 

[Phase]

Name=P2e_EJ

Gravity=Sonne, Venus, Mars, Jupiter

DrawBig=Sonne

Draw=Merkur, Venus, Erde, Mars, Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter

Zoom=5.5 AE

Next=P2e_J

 

[Phase]

Name=P2e_J

Gravity=Jupiter, Sonne, Mars, Saturn

DrawBig=Jupiter

Draw=V2_ErdeJupiter, V2_Jupiter, V2_JupiterSaturn

Focus=Jupiter

Epoch=7/9/1979 22:29:51

Zoom=.4 AE

Next=P_E1

 

///////////////////////////////////////////////////////////////////////////////

// Bahndaten Voyager I

 

[Path]

Name=V1_ErdeJupiter

Center=Sonne

System=FK4

Epoch=9/8/1977 09:08:17

a=745761000 km

e=.797783

i=1.032182°

OM=-17.565509°

o=-.767558°

M=.304932°

 

[Path]

Name=V1_Jupiter

Center=Jupiter

System=FK4

Epoch=3/5/1979 12:05:26

a=-1092356 km // Hier hat die NASA ein Minus vergessen!!!

e=1.318976

i=3.979134°

OM=119.454908°

o=-62.062795°

M=0.°

 

[Path]

Name=V1_JupiterSaturn

Center=Sonne

System=FK4

Epoch=4/24/1979 07:33:03

a=-593237000 km

e=2.302740

i=2.481580°

OM=112.975465°

o=-1.527299°

M=19.156329°

 

[Path]

Name=V1_Saturn

Center=Saturn

System=FK4

Epoch=11/12/1980 23:46:30

a=-166152 km

e=2.107561

i=65.893904°

OM=-167.106611°

o=-58.836017°

M=0.°

 

[Path]

Name=V1_PostSaturn

Center=Sonne

System=FK4

Epoch=1/1/1991 00:00:00

a=-480926000 km

e=3.724716

i=35.762854°

OM=178.197845°

o=-21.671355°

M=688.967795°

 

///////////////////////////////////////////////////////////////////////////////

// Bahndaten Voyager II

 

[Path]

Name=V2_ErdeJupiter

Center=Sonne

System=FK4

Epoch=8/23/1977 11:29:11

a=544470000 km

e=.724429

i=4.825717°

OM=-32.940520°

o=11.702680°

M=-.888403°

 

[Path]

Name=V2_Jupiter

Center=Jupiter

System=FK4

Epoch=7/9/1979 22:29:51

a=-2184140 km

e=1.330279

i=6.913454°

OM=147.253921°

o=-95.715216°

M=0.°

 

[Path]

Name=V2_JupiterSaturn

Center=Sonne

System=FK4

Epoch=9/15/1979 11:07:25

a=-2220315000 km

e=1.338264

i=2.582320°

OM=119.196938°

o=-9.170896°

M=4.798319°

 

[Path]

Name=V2_Saturn

Center=Saturn

System=FK4

Epoch=8/26/1981 03:24:57

a=-332965 km

e=1.482601

i=3.900931°

OM=60.314852°

o=88.222252°

M=0.°

 

[Path]

Name=V2_SaturnUranus

Center=Sonne

System=FK4

Epoch=10/17/1981 18:43:56

a=-579048000 km

e=3.480231

i=2.665128°

OM=76.860491°

o=112.289600°

M=10.350850°

 

[Path]

Name=V2_Uranus

Center=Uranus

System=FK4

Epoch=1/24/1986 17:59:47

a=-26694 km

e=5.014153

i=11.263200°

OM=-80.927265°

o=-86.390059°

M=0.°

 

[Path]

Name=V2_UranusNeptun

Center=Sonne

System=FK4

Epoch=6/9/1987 00:00:00

a=-448160000 km

e=5.806828

i=2.496223°

OM=-100.376161°

o=-46.104011°

M=315.018680°

 

[Path]

Name=V2_Neptun

Center=Neptun

System=FK4

Epoch=8/25/1989 03:56:36

a=-24480 km

e=2.194523

i=115.956093°

OM=114.507068°

o=113.992391°

M=0.°

 

[Path]

Name=V2_PostNeptun

Center=Sonne

System=FK4

Epoch=1/1/1991 00:00:00

a=-601124000 km

e=6.284578

i=78.810177°

OM=100.934989°

o=130.043962°

M=342.970736°

 

///////////////////////////////////////////////////////////////////////////////

// Die Sonne

 

[Sun]

Name=Sonne

m=1989100E24 kg

Color=100%,100%,0%

 

///////////////////////////////////////////////////////////////////////////////

// Planetendaten

 

[Planet]

name=Merkur

m=0.3302E24 kg

Color=40%,40%,40%

Epoch=01.01.1977 12:00:00

T=87.968865 days

a=0.3870982159 AU

e=0.2056295517

i=7.00460131°

LAN=48.05799382°

LP=76.91350356°

LM=75.66158769°

 

[Planet]

name=Venus

m=4.8685E24 kg

Color=50%,80%,20%

Epoch=01.01.1977 12:00:00

T=224.700372 days

a=0.7233287721 AU

e=0.0067623916

i=3.39437456°

LAN=76.47373858°

LP=131.29950501°

LM=43.03580379°

 

[Planet]

name=Erde

m=5.9736E24 kg

Color=10%,40%,100%

Epoch=23.08.1977 11:29:11

T=365.245924 days

a=0.9999860987 AU

e=0.0166753113

i=0.00021236°

LAN=88.72230132°

LP=101.20710457°

LM=-29.77647354°

 

[Planet]

name=Mars

m=0.64185E24 kg

Color=100%,30%,10%

Epoch=01.03.1978 12:00:00

T=686.899620 days

a=1.5237847045 AU

e=0.0933491946

i=1.84967880°

LAN=49.38972861°

LP=-24.39416858°

LM=138.77401589°

 

[Planet]

name=Jupiter

m=1898.6E24 kg

Color=100%,80%,20%

Epoch=09.07.1979 22:29:51

T=4330.749137 days

a=5.2030762315 AU

e=0.0481327371

i=1.30572853°

LAN=100.21867891°

LP=13.95773492°

LM=137.32497169°

 

[Planet]

name=Saturn

m=568.46E24 kg

Color=70%,70%,20%

Epoch=26.08.1981 03:24:57

T=10740.639983 days

a=9.5298531123 AU

e=0.0539926885

i=2.48583222°

LAN=113.47184925°

LP=91.85293855°

LM=-168.51036774°

 

[Planet]

name=Uranus

m=86.832E24 kg

Color=10%,30%,100%

Epoch=24.01.1986 17:59:47

T=30585.050012 days

a=19.1918326896 AU

e=0.0472619577

i=0.77211238°

LAN=74.14787642°

LP=168.55034430°

LM=-101.25359572°

 

[Planet]

name=Neptun

m=102.43E24 kg

Color=0%,0%,70%

Epoch=25.08.1989 03:56:36

T=59810.651135 days

a=30.0690811943 AU

e=0.0087350859

i=1.77324314°

LAN=130.90350061°

LP=38.59311525°

LM=-78.68818047°

 

[Planet]

name=Pluto

m=0.0125E24 kg

Color=20%,20%,20%

Epoch=01.01.2000 12:00:00

T=90465 days

a=39.48168677 AU

e=0.24880766

i=17.14175°

LAN=110.30347°

LP=224.06676°

LM=238.92881°

7            Der Programmablauf

7.1       Bedienung des Programms

Das Programm kann von Windows aus mit einem Doppelklick auf die Verknüpfung „Voyager starten“ im Ordner „Programm“ der CD gestartet werden. Um Parameter wie Datenbankdatei und Init-Block anzugeben eignet sich die Verknüpfung „Voyager mit Parametern starten“. Natürlich kann auch die MS-DOS Eingabeaufforderung verwendet werden.

Ich gehe jetzt vom Start ohne Parameter aus. es erscheint die Liste der verfügbaren Init-Blöcke. Es sind hier beide Voyager-Sonden aufgelistet. Zu jeder Sonde gibt es drei verschiedene Flugbahnmodelle. „Informativ“ zeigt während dem Flug alle Bahnelemente an, die mit dieser Phase in Verbindung stehen. Zwischen den Flugphasen wird die Simulation oft mit korrigierten Werten initialisiert, um auf der Bahn zu bleiben.

„Kosmetisch“ stellt praktisch die gleichen Situationen dar, jedoch werden die Bahnelemente nicht angezeigt, so dass auch keine Abweichung zu sehen ist.

„Ehrlich“ führt die Simulation ohne zwischenzeitliche Initialisierungen durch, was sehr schnell zu starken Abweichungen von den Vorgaben führt.

Die Simulation startet nun im Pausemodus, während dem die aktuelle Zeit angezeigt wird. Der Pausemodus wird mit der Leertaste ein- und ausgeschaltet. ESC dient zum Abbrechen der Simulation. Jede andere Taste kann benutzt werden, um im Pause-Modus die Simulation bis zur nächsten Bildschirmaktualisierung fortzusetzen.

7.2       Probleme

7.2.1     Mangelhafte Bahntreue der Simulation

Es zeigt sich, dass trotz der hohen Genauigkeit der Simulation die Sonden vom Weg abkommen. Dies kann auf mehrere Ursachen zurückgeführt werden, vermutlich ist die wahre Ursache eine Kombination dieser Fehlerquellen.

7.2.2     Probleme bei der Iteration

Die Simulation rechnet zwar mit für normale Verhältnisse sehr hoher Genauigkeit, trotzdem ist es denkbar, dass diese Genauigkeit für die Aufgabenstellung nicht genügt, da schon ein kleiner Fehler von wenigen Metern vor einem Swing-by nach dem Swing-by einen stark unterschiedlichen Winkel und damit am Ende der nächsten Flugphase einen sehr großen Fehler verursachen kann. So ist ein Kompromiß zu finden zwischen kleinen Zeitschritten, die die Bahnkrümmung gut nachbilden, und großen Zeitschritten, die die beim Runden auftretenden Änderungen verringern, da die addierten Werte verhältnismäßig größer sind. Außerdem nimmt die Energie der Sonde mit der zeit von sich aus minimal zu, da durch endlich kleine Zeitschritte die Bewegung immer ein kleines Bisschen geradlinig nach Außen verläuft, während die reale Kurve immer gekrümmt ist.

7.2.3     Ungenaue Ausgangsdaten

Doch auch die höchste Simulationsgenauigkeit ist nutzlos, wenn die zur Verfügung stehenden Daten nicht die entsprechende Genauigkeit besitzen. So sind meine Werte besonders was die Planetenmassen angeht nicht sehr genau.

7.2.4     Zwischenzündungen

Es ist anzunehmen, dass auch die Voyager-Sonden nicht so exakt gestartet wurden, dass sie danach von alleine auf der Bahn geblieben wären. Es ist daher sehr wahrscheinlich, dass die Voyager-Sonden während des Fluges durch kurze Zündungen der Bordtriebwerke auf der Bahn gehalten wurden. Da mir über derartige Zündungen keine genauen Daten vorliegen, sind diese in der Simulation nicht zu berücksichtigen.

7.2.5     Weitere Fehlerquellen

Zu diesen Fehlerquellen kommen weitere wie relativistische Effekte, der Einfluss des Sonnenwindes, die Inhomogenität der planetaren Gravitationsfelder, die Eigenbewegung der Sonne und viele weitere Ursachen.

Durch die weiten Entfernungen und die Hebelwirkung des Swing-by können sehr kleine Fehler sich schnell drastisch vergrößern.

Die beschriebenen Probleme sind also wohl kaum zu umgehen.

7.3       Möglichkeiten der Weiterentwicklung

Durch seinen modularen Aufbau ist das Programm gut zu erweitern. Es sind viele Erweiterungsmöglichkeiten Denkbar. So wäre im Bereich der grafischen Ausgabe viel zu tun. Der Anfang wäre eine einstellbare Löschung alter Positionen, so dass nur ein einzelner Punkt an der aktuellen Position eines Objekts angezeigt werden würde. Es wäre verhältnismäßig leicht, die Koordinaten des Beobachters einzulesen und mit ihrer Hilfe auch gekippte und gedrehte Ansichten der Ekliptik darzustellen. Die Krönung wäre die bereits beschriebene Erstellung dreidimensionaler Animationen.

Im Bereich der Iteration könnte man intelligentere und weniger fehleranfällige Algorithmen suchen.

Um Korrekturzündungen zu realisieren wäre eine von CPhase abgeleitete Klasse denkbar, welche die Sonde über kurze Zeit mit aktiver Beschleunigung simuliert. Doch müssten auch Daten gefunden werden, die diese Beschleunigungsphasen genau beschreiben.

8            Anhang

8.1       Überblick über die verwendeten Symbole

Um den Überblick zu erleichtern finden sich hier die Bedeutungen aller mathematischen Symbole, die ich verwende.

     Vom Fahrstrahl des bewegten Körpers überstrichene Fläche.

      Große Halbachse einer Kegelschnittfigur.

      Kleine Halbachse einer Kegelschnittfigur.

     Konstante in der Lösung der Differentialgleichung.

     Energie. Ohne Index die Gesamtenergie, sonst aus dem Index ersichtlich.

     Kraft auf den bewegten Körper (Sonde). .

     Gravitationskonstante. .

      Drehimpuls pro Masse, s. (7).

      Konstante in der Definition eines Kegelschnittes.

      Drehimpuls. .

    Mittlere Anomalie. Normalerweise mit M bezeichnet.

    Drehmoment. !

    Masse des Zentralkörpers (Sonne, Planet). !

     Masse des bewegten Körpers (Sonde).

      Der Nullvektor.

      Ortsvektor des bewegten Körpers, wenn der ruhende im Koordinatenursprung liegt.  ist Bestandteil der Polarkoordinaten.

      Substitutionsvariable. .

     Geschwindigkeit eines Planeten.

     Zwei Bedeutungen: entweder Gravitationspotential oder .

      Geschwindigkeit. .

      Numerische Exzentrizität, Konstante in der Definition eines Kegelschnittes.

      Konstante in der Lösung der Differentialgleichung.

      Mittelpunktswinkel, Bestandteil der Polarkoordinaten.

     Zwei Bedeutungen: entweder Winkelgeschwindigkeit () oder Perihelargument.

8.2       Literaturverzeichnis

Greiner, W.

Mechanik Teil 1

Thun; Frankfurt

H. Deutsch

19895

Meeus, J.

Astronomische Algorithmen

Leipzig; Berlin; Heidelberg

J. A. Barth

19942

Giese, R.H.

AKADEMIE-BERICHT
Moderne Astrophysik

Dillingen

Akademie für Lehrerfortbildung

o.J.

Barth, Mühlbauer, Nikol, Wörle

Mathematische Formeln und Definitionen

München

BSV,
J. Lindauer

19976

Hammer, Hammer

Physikalische Formeln und Tabellen

München

J. Lindauer

19976

Lexikonredaktion des Bib. Inst.

Das Große DUDEN-LEXIKON in acht Bänden

Mannheim

Bibliographisches Institut

1966

Schirawski, N.

Physikalische Konstanten: Diese Zahlen halten die Welt zusammen, in: P.M. 7/1999

München

Gruner + Jahr
AG & Co

1999

Jet Propulsion Laboratory, NASA

Voyager Project Home Page

http://vraptor.jpl.nasa.gov
/voyager/voyager.html

1999

Matousek, S.

JPL

Voyager 1 and 2 hyperbolic orbital elements

http://vraptor.jpl.nasa.gov
/voyager/vgrele_txt.html

1989

Williams, Dr. D. R.

Planetary Fact Sheets

http://nssdc.gsfc.nasa.gov
/planetary/planetfact.html

1997

Dings, Prof. M.

VSOP87C.DLL

von: http://members.aol.com
/DingsHMTSB/asappsen.htm

1997

Weisstein, E. W.

Treasure Troves of Science

http://www.treasure-troves.com

1996

 

Die ersten drei Bücher finden sich in der Systematik der Kreisbücherei Pfaffenhofen als „Uck GRE“, „Ubk 5 MEE“ sowie „Ubl 2 MOD“.

Bei den Internetquellen sind andere Seiten im selben Verzeichnis und Unterverzeichnissen mit eingeschlossen. Soweit möglich finden sich Kopien dieser Internetseiten auf der beiliegenden CD.


8.3       Quellenangaben


8.4       Erklärung

Ich erkläre, dass ich die Facharbeit ohne fremde Hilfe angefertigt und nur die im Literaturverzeichnis angeführten Quellen und Hilfsmittel benützt habe.

 

Wolnzach, den 1.2.2000

 

 

(Martin von Gagern)

 



[a]    Page of book (Newton, System of the World), NAIC
http://vraptor.jpl.nasa.gov/voyager/record_images/image111.gif

[b]   Greiner, Mechanik Teil 1, Abschnitt II, Kapitel 16, 17, 25, 26, 28, S. 141-358

[c]    Schirawski, Physikalische Konstante, P.M. 7/1999, Titelbild S. 42

[d]   A heliocentric view of the Voyager Spacecraft Trajectories,
http://vraptor.jpl.nasa.gov/voyager/images/tour.gif

[e]    Mathematische Formeln und Definitionen, S. 42

[f]    Meeus, Astronomische Algorithmen, Kapitel 31, Seite223-226

[g]   Dings, VSOP87C.DLL

[h]   Meeus, Astronomische Algorithmen, S. 225

[i]    Anmerkung von D. K. Yeomans, Vorsitzender des IAU System Transition Committee, an Richard West, Präsident der IAU Commission 20, am 10. August 1990.
Zitiert in Meeus, Astronomische Algorithmen, S. 165

[j]    Mathematische Formeln und Definitionen, S. 91f.