|
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
2 Das Gravitationsgesetz und seine Folgen
2.1 Die Grundlage: Newtons Gravitationsgesetz
2.6 Herleitung der Bahnfiguren
2.6.3 Alternative Darstellung der Kraft
2.6.4 Das Lösen der Differentialgleichung
2.6.5 Deutung als Kegelschnittfigur
3.1 Qualitative Beschreibung des Vorgangs
3.3 Berechnung des Energiegewinns
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.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.5 Die schrittweise Simulation
5.2 Grundsätzliches zu C und C
5.3 Objektorientierte Programmierung
5.3.1 Grundlagen der objektorientierten Programmierung
6.1 Übersicht über die Hierarchie der Klassen
6.3.1 Allgemeines über überladene Operatoren
6.4 Die Klasse CDataElement und davon abgeleitete
6.4.1 Zugriff auf die Datenelemente der Datenbankblöcke
6.5.1 Minimalanforderung für jeden Block
6.6.1 Einstellungen für die Darstellung
6.6.2 Darstellung mit der Klasse CPoint
6.7.2 Die Berechnung der Beschleunigung
6.8.2 Die Berechnung der Skelettdaten
6.9.2 CPath im Unterschied zu CPlanet
6.9.3 Die Geschwindigkeit im Perihelpunkt
6.10.3 Möglichkeiten zur Angabe der Startwerte
6.11.2 Die Darstellungsperspektive
6.11.3 Das Ende einer Flugphase
6.12.2 Koordination der Blöcke
7.2.1 Mangelhafte Bahntreue der Simulation
7.2.2 Probleme bei der Iteration
7.3 Möglichkeiten der Weiterentwicklung
8.1 Überblick über die verwendeten Symbole
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.
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:
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.
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)
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)
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.
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:
Natürlich gilt der Energieerhaltungssatz.
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:
Einsetzen in die Energiegleichung (11) gibt:
.
Es zeigt sich, dass durch Substitution von
leichter weiter
zu rechnen ist.
so lässt sich die Gesamtenergie schreiben als:
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:
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:
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:
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:
.
Nun ist aber die allgemeine Form einer Kegelschnittfigur in Polarkoordinaten:
Dabei ist
die numerische Exzentrität,
wodurch der Typ der Bahn festgelegt ist:
ergibt einen Kreis,
eine Ellipse,
eine Parabel und
eine Hyperbel.
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
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:
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
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.
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:
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 ![]()
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]

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.
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.
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
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.
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.
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).
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.
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).
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
.
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.

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:
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.
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
):
![]()
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.
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
.
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.
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]
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.

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.
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"
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.
// 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;
};
// 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);
}
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.
// 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
};
// 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]);
}
|
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.
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.
// 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
};
// 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);
}
|
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.
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.
// 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
};
// 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;
}
|
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.
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.
// 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; }
};
// 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;
}
|
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 |
|
|
LP |
Winkel |
|
|
LM |
Winkel |
|
|
Precision |
Ganzzahl |
Anzahl der Skelettpunkte für die Interpolation |
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.
// 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);
};
// 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;
}
|
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 |
|
|
o |
Winkel |
|
|
M |
Winkel |
|
|
System |
Zeichenfolge |
FK4 oder FK5 |
|
Precision |
Ganzzahl |
Anzahl der Skelettpunkte für die Interpolation |
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.
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).
// 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);
};
// 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;
}
|
DispName |
Zeichenfolge |
Wird in der Auswahl angezeigt |
|
Start |
CPhase |
Name der ersten Phase |
|
t |
Zeitpunkt |
|
|
rx, ry, rz |
Entfernung |
|
|
vxdt, vydt, vzdt |
Entfernung |
|
|
dt |
Zeitspanne |
Zur Bestimmung der bei |
|
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 |
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.
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.
// 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
};
#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);
}
|
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 |
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.
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.
// 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)
};
// 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");
}
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.
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.
// 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
}
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°
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.
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.
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.
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.
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.
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.
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.
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.
|
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 |
Dillingen |
Akademie für Lehrerfortbildung |
o.J. |
|
Barth, Mühlbauer, Nikol, Wörle |
Mathematische Formeln und Definitionen |
München |
BSV, |
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 |
1999 |
|
Jet Propulsion Laboratory, NASA |
Voyager Project Home Page |
1999 |
||
|
Matousek, S. JPL |
Voyager 1 and 2 hyperbolic orbital elements |
1989 |
||
|
Williams, Dr. D. R. |
Planetary Fact Sheets |
1997 |
||
|
Dings, Prof. M. |
VSOP87C.DLL |
1997 |
||
|
Weisstein, E. W. |
Treasure Troves of Science |
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.
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.