Betrachtet man die vorhergehenden Themen im Zusammenhang, entsteht ein Datenfluss von den vorliegenden Eingabedaten im GRIB- und Shapefile-Format und ihrer computergrafischen Verarbeitung in Kapitel 4 über das Datenaustauschformat XML, der dazugehörigen Transformationssprache XSLT und dem Flash-Generator Saxess Wave in Kapitel 3 bis hin zum fertigen Flash-Film und seinem Dateiformat SWF in Kapitel 2. Abbildung 3.6 zeigte bereits einige der genannten Komponenten als Teil des angestrebten Visualisierungsprozesses.
Dieser Prozess geht jedoch davon aus, dass die Eingabedaten erstens in einem XML-Format vorliegen und zweitens Grafikobjekte enthalten, die bereits für eine Visualisierung vorbereitet sind. Dies ist im Allgemeinen aber nicht der Fall, wie schon in der Einleitung von Kapitel 4 angemerkt. Vielmehr ist z.B. bei den Wetterdaten des DWD die Anwendung verschiedener Transformationen - Vektorisierung der Rasterdaten, Änderung der Kartenprojektion, Clipping und andere - notwendig, bevor schließlich Polygone die gewünschten Temperatur- oder Niederschlags-Isoflächen für einen bestimmten Ausschnitt widerspiegeln und zudem Koordinatenangaben enthalten, die direkt als Pixelpositionen innerhalb eines Flash-Films verwendet werden können.
Die XML-Softwareprodukte der Apache Software Foundation, der XML-Parser Xerces-J und der XSLT-Prozessor Xalan-Java, unterstützen das Einlesen von XML-Dateien und die Umwandlung der Daten in die XML-Ausprägung SWFML, aus der letztlich mittels Saxess Wave ein Flash-Film generiert werden kann. Sämtliche Schritte, die aber vorher zur Erzeugung einer XML-Quelldatei mit dem Inhalt des Flash-Films erfolgen müssen, sind in ,,Handarbeit`` durchzuführen.
Das folgende Kapitel stellt daher die Realisierung eines selbst entwickelten, möglichst flexiblen Mechanismus vor, der die Bearbeitung von in XML codierten Grafikobjekten ermöglicht.
Die Implementation erfolgt, wie in Abschnitt 2.5 festgelegt, in Java. Eigene Klassen sind in Java-Paketen organisiert, die mit der Bezeichnung de.flashweather beginnen. Genauere Angaben zu ihrem Funktionsumfang befinden sich in der mit Javadoc erzeugten API-Dokumentation auf der beigefügten CD-ROM.
Da sich aufwendigere Transformationen und computergrafische Algorithmen nur auf Grafikobjekte anwenden lassen, die auch als solche im Speicher vorliegen und nicht etwa lediglich in Textform als Teil eines XML-Dokuments, werden Java-Klassen benötigt, die eine Schnittstelle zwischen Java und XML schaffen. Sie werden im Folgenden die Java-Grafikobjekte genannt. Diese Klassen sind in der Lage, Teile eines XML-Baums selbstständig zu erfassen und daraus das dazugehörige Grafikobjekt mit seinen Koordinaten und Attributen aufzubauen. Umgekehrt ermöglichen Methoden, die ursprüngliche XML-Struktur eines Grafikobjekts wieder in einen XML-Baum zurückzuschreiben. Die Verwendung der Java-Grafikobjekte ohne den Bezug zu einer XML-Quelle ist ebenfalls möglich.
Jedes Java-Grafikobjekt stellt über eine Programmierschnittstelle (API) Methoden zur Verfügung, die z.B. den Zugriff auf die Definitionspunkte des Objekts erlauben oder einfache Transformationen durchführen. Diese Schnittstelle wird von externen Transformationsklassen genutzt, um aufwendigere Transformationen der Grafikobjekte (Clipping, Projektion) durchzuführen. Eine entsprechende Erweiterung des bisherigen Datenflusses zeigt Abbildung 5.1.
Dieser Entwurf ermöglicht, dass ein mehrfacher Wechsel zwischen XML-Repräsentation und Java-Grafikobjekt stattfinden kann. Jeder Lese- und Schreibvorgang ist selbstverständlich zeitaufwendig, kann aber eventuell beabsichtigt sein. Werden z.B. die Daten einer Deutschlandkarte bereits in der gewünschten Projektion für eine spätere Visualisierung bereitgestellt, ist für die Darstellung unterschiedlicher Ausschnitte dieser Karte erneut eine Umwandlung der XML-Daten in Java-Grafikobjekte nötig.
Damit Java-Klassen ein textbasiertes Format wie XML lesen und verarbeiten können, müssen zumindest gewisse Grundregeln festgelegt werden. Fest steht, dass die XML-Dateien mit Hilfe von beliebigen Tags codierte Grafikobjekte und eventuell weitere Informationen enthalten. Eine Java-Klasse muss jedoch die Namen der XML-Tags und -Attribute kennen, um aus ihnen die korrekten Informationen herauslesen und das entsprechende Grafikobjekt konstruieren zu können. Auch die Struktur bzw. Verschachtelung der Tags ineinander ist von Bedeutung. So lautet z.B. die Definition eines zweidimensionalen Polygons in SWFML, dem Eingabeformat von Saxess Wave:
<polygon
Polygon-Tag
ID="poly1"
eindeutige Kennung
color="C0FFB080"
Füllfarbe des Polygons lw="1" lc="00FFFFFF">
Stärke und Farbe der Umrandung <point x="20" y="20"/>
Eckpunkte mit <point x="50" y="20"/>
... ganzzahligen Koordinaten ...
weitere Eckpunkte </polygon>
Ende des Polygon-Tags
< objecttag
beliebiges Tag des Grafikobjekts
attr1="value1"
Attribut
...
weitere Attribute
< infotag attr1=".." ... />
Tag, das keinen Punkt enthält
< pointtag xattr="#.#"
Punkt mit
yattr="#.#" />
... Gleitkomma-Koordinaten
...
weitere Punkte
</ objecttag >
Ende des Grafikobjekt-Tags
Die Einschränkung, dass Grafikobjekte in XML in der oben genannten Datenstruktur definiert sein müssen, ist notwendig, damit sich Java-Klassen realisieren lassen, die die XML-Daten einlesen und interpretieren können. Die Fähigkeit der Klassen, XML-Formate mit beliebigen Tag- und Attributnamen verarbeiten zu können, kann nicht vollständig umgesetzt werden. Sie kann nur insoweit verwirklicht werden, dass die Klassen für eine beliebige Ausprägung des Formats konfigurierbar sind. Ein Einlesen von in XML codierten Grafikobjekten ist also nur möglich, wenn die vorgeschriebene Struktur der Daten eingehalten wird und die Namen der Tags und Attribute bekannt sind.
Dabei ist die oben gezeigte Datenstruktur für die Speicherung von Grafikobjekten
in XML auf die Anforderungen der vorliegenden Arbeit angepasst. Denn die verwendeten
Grafikobjekte sind zweidimensionale Polygone, Linien (bestehend aus mehreren
Punkten) und Punkte. Spezialfälle wie Kreise oder Rechtecke, die durch einen
Radius oder gegenüberliegende Eckpunkte definiert sind, kommen nicht vor.
Für die Verwaltung zweidimensionaler Raster- und Vektordaten in Java werden zwei unterschiedliche Gruppen von Grafikobjekten benötigt: Einfache Punkte und komplexe Grafikobjekte wie z.B. Polygone oder Linien, die sich aus den zuerst genannten Punkten zusammensetzen. Beide Gruppen von Grafikobjekten sollen gleichermaßen die grundlegenden Transformationen (Verschiebung, Skalierung, Drehung) beherrschen.
Einfache Punkte enthalten die Angaben zu ihrer Position innerhalb eines
Koordinatensystems und speichern die Werte der x- und y-Koordinate. Ein Punkt,
der zu einem Raster (engl. grid) gehört, verfügt zudem über die Information,
welchen skalaren Wert der Parameter (z.B. Temperatur) an der betreffenden Stelle
annimmt, und speichert ihn zusätzlich zu den Koordinatenangaben. Eine Speicherung
von zusätzlichen Attributen für einfache Punkte ist nicht vorgesehen. Die beiden
Punkt-Objekte entsprechen damit XML-Ausdrücken der Form:
<point x="2.5" y="3.7"/>
<gridpoint x="2.5" y="3.7" value="12.4"/>
Dazu ein Beispiel: Ein See, dessen Umriss in XML durch ein einleitendes lake-Tag
und untergeordnete Definitionspunkte beschrieben ist, besitzt einen Namen als
Attribut des lake-Tags: <lake name= "Dümmer ">.
Dieser Name ist für die grafische Bearbeitung des Seeumrisses nicht von Bedeutung,
wird aber von der Java-Klasse zwischengespeichert, um eine spätere Ausgabe des
Sees mit allen seinen Attributen zu ermöglichen. Eventuelle Attribute
der Defintionspunkte (außer den Koordinaten) gehen jedoch durch die Bearbeitung
verloren. Abbildung 5.2 veranschaulicht das Wechselspiel
von Java und XML.
Die Einschränkung, dass einfache Punkte außer ihrer Position und (bei einem
Rasterpunkt) dem Wert des Parameters keine weiteren Attribute besitzen dürfen,
muss aus Performancegründen gemacht werden. Ein Raster, das die kompletten Daten
einer Stundenprognose des DWD enthält, umfasst z.B. 325 * 325 = 105.625
Punkte. Ein immenser Speicherbedarf wäre vorhersehbar, dürfte jeder dieser Punkte
zusätzliche Attribute beherbergen.
Aus den Überlegungen des vorhergehenden Abschnitts ergibt sich eine Klassenhierarchie
für die Implementation der benötigten Java-Grafikobjekte. Abbildung 5.3
stellt sie in einer Baumstruktur dar.
Die abstrakte Basisklasse aller Grafikobjekte heißt GraphicObject.
Einfache Punkte sind Point-Objekte oder sie stammen von dieser Klasse
ab. Komplexe Grafikobjekte werden von der abstrakten Klasse Shape
repräsentiert, von der speziellere Typen abstammen.
Die Grafikobjekt-Klassen des Java-Pakets de.flashweather.graph2d.base
werden nun im Einzelnen vorgestellt:
Alle von GraphicObject abstammenden, nicht-abstrakten Klassen verfügen
über mindestens drei Konstruktoren:
Tabelle 5.1:
In Grafikobjekt-Klassen bekannte XML-Merkmale
Die Forderung, dass alle Grafikobjekt-Klassen dennoch beliebige XML-Tags
verarbeiten können, lässt sich nur realisieren, wenn die der Klasse bekannten
XML-Merkmale konfigurierbar bleiben. Deshalb verfügt jede der genannten Klassen
über Klassenvariablen, in denen der Name ihres XML-Tags und die Attributnamen
gespeichert sind. Z.B. verfügt die Klasse Point über die Klassenvariablen
XML_TAG, XML_ATTR_X und XML_ATTR_Y. Alle diese
Klassenvariablen sind als public deklariert und dürfen von jeder externen
Klasse an die eigenen Bedürfnisse angepasst werden.
Quellcode 5.1: XML-Ausgabe eines Java-Grafikobjekts
Quellcode 5.1 verdeutlicht die Verwendung der
Klassenvariablen. Das erzeugte Document-Objekt enthält zum Ende
der Ausführung folgendes XML-Dokument:
Wird ein neues Grafikobjekt jedoch aus einem XML-Teilbaum erzeugt, werden
die Werte der Klassenvariablen ebenfalls bei der Initialisierung übernommen.
Gleichzeitig werden sie aber auch für die korrekte Zuordnung der Attribute und
Sohnknoten der XML-Quelle zu den internen Objektvariablen benötigt. Die vorherige
Festlegung des einleitenden XML-Tags für das Grafikobjekt ist nicht erforderlich
(schadet aber auch nicht), da der Konstruktor diesen Wert dem eindeutigen Wurzelelement
des übergebenen XML-Teilbaums entnehmen kann (im Beispiel: stadt).
Der Abschnitt 4.1.1 über Landkartendaten und das Dateiformat
der ESRI Shapefiles [ESRI1998] deutete bereits an, dass das entsprechende
Lesemodul nicht wie alle anderen Programme dieser Diplomarbeit in Java, sondern
in Ansi-C implementiert ist. Es wird aber davon ausgegangen, dass die Kartendaten
aus den Shapefiles nur ein einziges Mal in ein XML-Format zu konvertieren sind,
da sich Landkarten nicht täglich ändern. Anschließend kann auf jedem Rechner
mit den XML-Daten weitergearbeitet werden.
Das Shapefile-Format besteht im Wesentlichen aus einer Datei mit der Endung
.shp, die die eigentlichen Vektorgrafikobjekte (Punkte, Linien oder
Polygone) enthält, und einer Datei mit der Endung .dbf, die im dBASE-Datenbankformat
die Attribute für jedes Grafikobjekt bereithält. Die ebenfalls in Abschnitt 4.1.1
bereits angesprochene kostenlose C-Bibliothek shapefile [Warm2000]
erleichert die Realisierung eines Lesemoduls für Shapefiles. Mit Hilfe ihrer
Programmierschnittstellen SHP-API und DBF-API konnte ein Konverter implementiert
werden, der gleichzeitig durch beide Dateien läuft und die Informationen - Grafikobjekte
und Attribute - zusammenführt. Eine Ausgabe der Daten erfolgt fortlaufend in
einem gültigen XML-Format, das MapML genannt wurde. Der Konverter heißt
entsprechend shp2mapml.
MapML ist keine offizielle XML-Ausprägung, sondern wurde speziell für die Anforderungen
dieser Diplomarbeit entworfen. Quellcode 5.2 zeigt seine
DTD.
Quellcode 5.2: MapML-DTD
Demnach besitzt eine Landkarte, die mit dem Tag map eingeleitet wird,
einen Typ (country, river, lake oder city)
und besteht aus mindestens einem part. Jeder part besitzt
eine Kennung, einen Typ und wahlweise eine Bezeichnung und eine Kategorie. Ein
part enthält mindestens einen Punkt als Tag mit dem Namen point.
Die Koordinaten von Punkten sind als Attribute des point-Tags definiert.
Die Definition der Stadt Hamburg mit ihren geografischen Koordinaten lautet
in MapML z.B.:
In Abschnitt 4.1.2 wurde das GRIB-Dateiformat (Gridded
Binary) [WMO1998] vorgestellt, das weltweit für die Speicherung von Wetterdaten
genutzt wird. Es setzt sich aus Blöcken, den GRIB-Records, zusammen.
Jeder Record enthält die Daten eines Parameters (z.B. Temperatur oder Luftdruck)
und besteht wiederum aus fünf Sektionen.
Obwohl GRIB als offener internationaler Standard definiert wurde, konnten im
Internet kaum Programme zum Auslesen des Dateiformats gefunden werden. Das mag
daran liegen, dass das Format sehr flexibel gehalten ist und alle möglichen
Parameter und beliebige Kartenprojektionen, in denen die Koordinaten der Rasterpunkte
vorliegen können, berücksichtigt. Ein C-Programm, das eine umfangreiche Unterstützung
von Parametertabellen und Rasterdefinitionen bietet und den Inhalt beliebiger
GRIB-Dateien verarbeiten kann, heißt wgrib [NCEP2000]. Es wurde
von einem Mitarbeiter des NCEP (National Climate Prediction Center der USA)
entwickelt. Leider listet das Programm die Informationen zum Inhalt der Records
nur in einer sehr kryptischen Form auf (siehe Abschnitt 4.1.2).
Die Rasterdaten, d.h. die Werte des Parameters an den Rasterpunkten, können
nur in separaten Dateien abgelegt und nicht über eine Schnittstelle direkt weiterverarbeitet
werden.
Da sich Wetterprognosedaten aber täglich oder sogar stündlich ändern, ist es
aus Speicherplatz- und Performancegründen nicht sinnvoll, die gesamten bitcodierten
Daten im Textformat auf Festplatte zu speichern. Vorteilhafter ist eine programminterne
Auswahl der benötigten Daten, die daraufhin für eine direkte Weiterverarbeitung
zur Verfügung stehen. Um eine solche Handhabung der Wetterdaten in Java zu ermöglichen,
wurde ein eigenes Lesemodul für GRIB-Dateien implementiert.
Abbildung 5.4 stellt die Klassen dar, aus denen sich
eine GRIB-Datei in Java zusammensetzt. Im Vergleich zum prozeduralen C-Programm
wgrib entsteht eine leicht zu überblickende Struktur, in der für jede
Detailstufe spezielle Java-Klassen existieren, die zusammengenommen die GRIB-Datei
ergeben.
Die Java-Klassen werden folgendermaßen verwendet: Um ein Raster mit den Werten
eines bestimmten Parameters zu bekommen, muss aus dem GribFile der
entsprechende GribRecord mit getRecord gewählt werden. Die
Klasse GribRecord verfügt bereits über die elementaren Methoden zum
Auslesen des GRIB-Records (getDescription, getGridShape, getLevel,
getTime, getType, getUnit), von denen getGridShape
die wichtigste Methode ist, da sie das eigentliche Raster in Form eines Java-Grafikobjekts
liefert. Noch detailliertere Informationen enthalten die Klassen der fünf Sektionen
des Records, die die Methoden getIS, getPDS, getGDS,
getBDS und getBMS liefern.
Ist nicht bekannt, welche Daten eine GRIB-Datei enthält bzw. in welchem Record
sich der gesuchte Parameter befindet, hilft ein kleines Tool (Quellcode 5.3).
Die toString-Methode von GribRecord liefert eine Kurzinformation
zum Inhalt des Records.
Quellcode 5.3: Hilfsklasse zum Anzeigen des Inhalts einer GRIB-Datei
Aufgrund der angesprochenen Vielfältigkeit des GRIB-Dateiformats besitzt die
eigene Java-Implementation eines Lesemoduls keinen Anspruch auf Vollständigkeit.
Die Tabelle der Parameter wurde dem Programm wgrib entnommen und nur
die häufig verwendeten Kartenprojektionen für die Definition des zugrunde liegenden
Rasters wurden implementiert. Einige speziellere Projektionen und Einstellungen
werden nicht unterstützt, doch konnten z.B. die von einem Internetrechner des
National Weather Service der USA [NWS2000] heruntergeladenen Wetterprognosedaten
sofort problemlos verarbeitet werden.
Als erstes Modul aus der Gruppe der Transformationsklassen soll im Folgenden
die Implementierung und Verwendung des Algorithmus zur Vektorisierung von Rasterdaten
aus Abschnitt 4.2 vorgestellt werden. Die einzelnen
Schritte des Algorithmus wurden bereits ausführlich beschrieben, so
dass nicht erneut auf den gesamten Ablauf eingegangen wird. Stattdessen sollen
einige interessante Passagen genauer erläutert werden, in denen die Arbeitsweise
des Algorithmus anhand des Programmcodes und der Kommentare nicht sofort ersichtlich
ist.
Die vollständige Implementation des Vektorisierungs-Algorithmus befindet sich
in der Klasse VectorizerImpl im Paket de.flashweather.graph2d.vectorize.
Der Algorithmus benötigt als Input die Rasterdaten in Form eines rechteckigen,
voll besetzten Feldes von zweidimensionalen Punkten und die Werte eines Parameters
an diesen Punkten. GridShape ist die Grafikobjekt-Klasse, die genau
für diesen Fall geschaffen wurde. Sie stellt ein rechteckiges Feld von GridPoint-Objekten
zur Verfügung, die jeweils über eine x-, eine y-Koordinate und einen skalaren
Wert verfügen.
Als zweite Eingabe benötigt der Algorithmus die Isowerte, welche als Array von
double-Werten übergeben werden. Aus den Isowerten entstehen Wertebereiche
(siehe Tabelle 5.2). Anhand dieser Wertebereiche und
der Werte des Parameters wird das Raster in Flächen zerteilt, in denen der Parameter
jeweils nur Werte eines Wertebereichs annimmt. Dabei werden bei der Erstellung
der Flächen bzw. für den Verlauf der Begrenzungslinien, nicht nur die Parameterwerte
direkt an den Rasterpunkten berücksichtigt, sondern auch die Werte auf den Gitterkanten
des Rasters durch lineare Interpolation hinzugezogen.
Tabelle 5.2:
Isowerte, Wertebereiche und Füllindizes
Die entstandenen Flächen, sie wurden im Abschnitt 4.2
als Isoflächen bezeichnet, werden von Isolinien und eventuell dem Rand
des Rasters begrenzt. Die begrenzenden Isolinien können dabei zu zwei unterschiedlichen
Isowerten gehören, da jede Isofläche die Parameterwerte eines Wertebereichs
enthält, der von zwei Isowerten (bzw. ) bestimmt
wird (vgl. Abschnitt 4.2.3). Diese Eigenschaft der
Isoflächen ist während der Implementierungsphase stets zu berücksichtigen.
Die Speicherung der Isoflächen in Java erfolgt durch PolygonShape-Objekte.
Entlang der Isolinie bzw. des Rasterrandes werden solange neue Polygonpunkte
eingesammelt, bis der Startpunkt des Polgyons erneut erreicht wird und sich
ein geschlossener Linienzug ergibt. Den Füllindex des Objekts bestimmt der Wertebereich
der repräsentierten Isofläche (siehe Tabelle 5.2).
Schwierigkeiten bei der Implementierung des Algorithmus machte nicht das Auffinden
der Isowerte und das Verfolgen der Isolinien durch das Raster, sondern z.B.
die korrekte Markierung der Gitterkanten, auf denen ein Isowert gefunden worden
war, und die durchgängige Beschriftung der Isolinien.
Das auf Snyders Artikel [SnyW1978] basierende line following
verfolgt den Verlauf einer Isolinie anhand der Gitterkanten durch das rechteckige
Raster. Alle Isowerte auf allen Gitterkanten müssen für die erfolgreiche Vektorisierung
der Rasterdaten gefunden werden und jeder interpolierte Isopunkt muss Bestandteil
einer Isolinie sein. Bereits gefundene Isowerte müssen dabei für die betroffene
Gitterkante vermerkt werden, damit sie nicht noch einmal berücksichtigt werden.
Eine Isolinie trennt jedoch zwei Isoflächen voneinander. Beide Flächen
beanspruchen sie als Begrenzungslinie. Deshalb muss es erlaubt sein, dass eine
Isolinie genau zweimal verwendet wird - für zwei verschiedene Isoflächen.
Diese unterschiedliche Verwendung derselben Isolinie kann während der Programmausführung
daran erkannt werden, dass eine Isolinie für die beiden beteiligten Flächen
einmal die untere Grenze des Wertebereichs darstellt und einmal die obere. Die
Klasse GridEdges stellt einige Methoden zur Verfügung, die das Markieren
von Gitterkanten eines GridShape während der Vektorisierung unterstützen.
Das geschilderte Phänomen tritt nur für Isoflächen auf, die mit dem Rand des
Rasters in Berührung sind. Die Isolinien aller inneren Polygone werden nur einmal
gefunden. Je nach Konfiguration des Vektorisierers werden sie jedoch anschließend
für die Bildung eines ,,Donuts`` aus der umschließenden Fläche (siehe
Abbildung 4.5) ein zweites
Mal verwendet.
Die Füllung einer Isofläche festzustellen, ist einfach, da sie ,,der Farbe``
bzw. dem Index des Wertebereichs entspricht (siehe Tabelle 5.2).
Doch wie sind Bezeichnungen zu vergeben? Üblicherweise werden nicht die Flächen
mit ihrem Wertebereich beschriftet, sondern die Isolinien (z.B. Isothermen,
Isobaren), d.h. die Begrenzungslinien mit dem Wert des Parameters versehen.
Für die Speicherung der Isoflächen werden PolygonShape-Objekte verwendet,
die sich anhand von LabelPoint-Objekten die Positionen von Beschriftungen
merken. Ein LabelPoint gibt jedoch nur eine Position an. Die Art der
Beschriftung bestimmt das Polygon selbst. Es wäre möglich gewesen, jedes LabelPoint-Objekt
mit einer Zeichenkette für die Speicherung eines individuellen Labels
auszustatten, doch als Bestandteil eines komplexen Grafikobjekts wäre diese
Individualität der Labelpunkte kaum zu verwalten gewesen.
Da Beschriftungen direkt auf dem Rand des Rasters nicht sinnvoll erscheinen
- sie ragen über den Rand der Rasterfläche hinaus und markieren eventuell keine
Isopunkte - beschränkt sich das Problem auf die Abschnitte der Begrenzungslinie
des Polygons innerhalb des Rasters. Dort wird das Polygon von Isolinien begrenzt,
die zu zwei verschiedenen Isowerten gehören können. Trotzdem sieht ein PolygonShape
nicht vor, zwischen zwei unterschiedlichen Beschriftungen bzw. zwischen zwei
unterschiedlichen Gruppen von LabelPoints zu differenzieren. Stattdessen
lautet die Regelung: Nur die Isolinie des niedrigeren Isowerts, der die untere
Grenze des Wertebereichs bestimmt, wird beschriftet bzw. nur dort werden Labelpunkte
gesetzt. Das Polygon erhält als Beschriftungsattribut den Wert des niedrigeren
Isowerts, das später an den Positionen der Labelpunkte erscheint. Das hat den
Vorteil, dass keine Linie aus Versehen doppelt mit Beschriftungen versehen wird.
Eine Ausnahme der Regelung gibt es allerdings. Sie tritt ein, wenn der Vektorisierer
so konfiguriert wurde, dass innere Polygone nicht aus ihrer umgebenen Fläche
herausgeschnitten werden. Die Isolinie, die ein inneres Polygon begrenzt,
wird in diesem Fall nur einmal verwendet und nicht beschriftet, wenn es sich
um die höherwertige Isolinie handelt. Hier muss die Ausnahme eintreten.
Abbildung 5.5
zeigt an einfachen Beispielen die Beschriftung der Isolinien für alle möglichen
Fälle. Die letzte Grafik zeigt den Spezialfall, bei dem ausnahmsweise die höherwertige
Isolinie beschriftet werden muss.
Die Klasse VectorizerImpl besitzt nur den leeren Konstruktor (warum
erklärt Abschnitt 5.6) und stellt folgende
Methoden für die Verwendung des Vektorisierungs-Algorithmus zur Verfügung:
Zu Beginn dieses Kapitels wurde ein allgemeines XML-Format für Grafikobjekte
und die dazugehörigen Klassen der Java-Grafikobjekte vorgestellt. Lesemodule
für Shapefiles und GRIB-Dateien liefern Karten- und Wetterdaten als XML-Dateien
oder direkt als GridShape-Objekt. Die weitere Verarbeitung der Daten
kann nun anhand von Tranformationen, die die Java-Objekte selbst ermöglichen,
oder mit Hilfe von Transformationsklassen erfolgen. Zu den Transformationsklassen
sollen die Klassen zählen, die auf den Java-Grafikobjekten des Pakets de.flashweather
.graph2d.base operieren, wie z.B. die Vektorisierungs-Klasse aus dem vorherigen
Abschnitt.
Dieser Abschnitt stellt die Implementation weiterer Transformationen und Transformationsklassen
vor.
Die in Abschnitt 4.4 vorgestellten elementaren
Transformationen Translation, Skalierung und Rotation beherrschen alle von
GraphicObject abstammenden Klassen - also alle Java-Grafikobjekte.
Die Skalierung und die Rotation erfolgen dabei bezüglich des Ursprungs des Koordinatensystems.
Die zur Verfügung stehenden Methoden lauten im Einzelnen:
Eine spezielle Transformation von Rasterdaten ist die Generalisierung.
Für ein rechteckiges Raster fasst sie zeilen- und spaltenweise eine vorgegebene
Anzahl Rasterpunkte zu einem einzigen neuen Punkt zusammen. Sowohl für die Koordinaten
der Rasterpunkte als auch für die Parameterwerte an den Rasterpunkten wird der
Mittelwert gebildet. Die Dimension des Rasters wird verkleinert, wie Abbildung 5.6
an einem Beispiel verdeutlicht.
Die Klasse GridShape verfügt bereits über eine Methode getGeneralized(int
col, int row), die ein generalisiertes GridShape-Objekt zurückliefert,
bei dem jeweils die Rasterpunkte von col Spalten und row Zeilen
des Ausgangsobjekts zu einem Wert zusammengefasst wurden. Das aktuelle Objekt
bleibt dabei unverändert.
Die elementaren Transformationen und die Generalisierung eines Rasters sind
direkt in den Klassen der Java-Grafikobjekte implementiert. Die folgenden
Transformationen Clipping und Projektion existieren stattdessen
in externen Klassen. Sie implementieren komplizierte Algorithmen in umfangreichen
Methoden, die nicht mit dem Programmcode der Grafikobjekte vermischt werden
sollen. Die Transformationsklassen werden erst bei Bedarf geladen und können
leicht ausgewechselt werden, ohne Veränderungen an den Grafikobjekten selbst
vorzunehmen.
Alle in Abschnitt 4.4 vorgestellten Clipping-Algorithmen
implementiert die Klasse ClipperImpl aus dem Paket de.flashweather.graph2d.clip.
Zur Erzeugung eines Clipping-Objekts existiert nur der leere Konstruktor. Die
Köpfe der Methoden sind durch das Interface Clipper vorgegeben, das
eine allgemeine Schnittstelle für Clipping-Algorithmen im Zusammenhang mit den
vorliegenden Java-Grafikobjekten spezifiziert. Die Methoden lauten (ohne
die Koordinaten des rechteckigen Clipping-Fensters):
Das Java-Paket de.flashweather.graph2d.project enthält mehrere Interfaces
und Klassen, die Kartenprojektionen und verwandte Transformationen zur Verfügung
stellen. Die Klassenhierarchie ist in Abbildung 5.7
dargestellt.
Das Interface Projection definiert für alle Projektionsklassen die
beiden Methoden:
Im Fall der Klasse Geo2Lambert, die das AzimuthalProjection-Interface
implementiert, handelt es sich bei den Koordinaten des Berührpunkts um geografische
Koordinaten. Die project-Methoden der Klasse wandeln geografische Koordinaten
in die Bildkoordinaten des flächentreuen azimuthalen Entwurfs von Lambert um
(siehe Abbildung 4.13).
Das zweite von Projection abstammende Interface RotateGridProjection
spezifiziert keine echte Projektion der Erdkugeloberfläche auf eine Projektionsebene,
sondern eine eventuell vor einer Projektion notwendige Rotation des Längen-
und Breitengradnetzes. Deshalb befindet sich das Interface und die Klasse RotGeoLLGrid,
eine Implementation des Interface, im Paket der Projektionen, obwohl die beiden
eine spezielle 3D-Transformation definieren und implementieren.
RotateGridProjection erweitert das Projection-Interface um
drei Methoden: addX-, addY- und addZRotation(double
angle), mit denen die erforderlichen Rotationen eingestellt werden. Intern
speichert sie die Klasse RotGeoLLGrid in einem TransformationMatrix-Objekt,
einer 3x3-Matrix.
Während der Projektion wird für jeden Punkt als erstes ein SurfacePoint
erzeugt. Der SurfacePoint repräsentiert denselben Punkt auf der Oberfläche
der Erdkugel, verwendet aber nicht Längen- und Breitengrad zur Bestimmung der
Position, sondern x-, y- und z-Koordinate. An diesem 3D-Repräsentanten des eigentlichen
Punkts werden die Rotationen durchgeführt. Anschließend werden die drei Koordinaten
wieder in die Punktkoordinaten, geografische Länge und Breite, umgerechnet und
dem ursprünglichen Punkt zugewiesen. So erfolgt Punkt für Punkt die Drehung
des Gradnetzes der Erde bzw. die Verschiebung des Grafikobjekts auf der Kugeloberfläche.
Die Implementierung der Rotations-,,Projektion`` war nötig, da die Wetterdaten
des DWD an Rasterpunkten definiert sind, deren Koordinaten die Position innerhalb
eines gedrehten Längen- und Breitengradnetzes angeben. Das Gradnetz der Erde
wurde so auf der Erdoberfläche positioniert, dass Äquator und Null-Meridian
mitten durch Deutschland laufen. Das hat den Vorteil, dass die Schrittweite
zwischen zwei benachbarten Rasterpunkten im gesamten Raster etwa gleich bleibt.
Denn in der Nähe des Äquators verlaufen die Längengrade noch relativ parallel
zueinander, was zur Folge hat, dass auch die Schrittweite zwischen zwei Rasterpunkten,
von denen immer gleich viele je Längengrad definiert sind, etwa konstant bleibt.
Eine Umrechnung aller Rasterpunkte in die wahren Längen- und Breitengradkoordinaten
muss erfolgen, bevor eine echte Projektion wie Geo2Lambert angewendet
werden kann.
Abbildung 5.8 zeigt noch einmal alle Transformationen
und Transformationsklassen im Überblick.
Die vorherigen Abschnitte stellten Java-Klassen vor, die Grafikobjekte repräsentieren,
Wetter- und Kartendaten auslesen und Transformationen auf den Grafikobjekten
ausführen. Prinzipiell existieren nun alle Komponenten, die für die Erzeugung
eines Flash-Films benötigt werden. Jedoch muss eine Konfiguration der Transformationen
noch programmintern erfolgen. Eine Einschränkung, die sehr an das SWF-SDK von
Macromedia erinnert, da jede Änderung eines Flash-Films nur durch die Anpassung
des Programmcodes, der den Film erzeugt, erreicht werden kann.
Das fehlende Modul im Visualisierungsprozess (Abbildung 5.1) ist die externe Konfiguration
der Transformationen und deren Reihenfolge. In diesem Abschnitt werden deshalb
zwei weitere Java-Klassen vorgestellt, die eine Konfigurationsdatei verarbeiten
bzw. anhand ihres Inhalts die entsprechenden Transformationen ausführen.
Die Konfiguration von nicht-interaktiven Kommandozeilenprogrammen erfolgt entweder
über Parameter, die dem Programm beim Start mitgegeben werden, oder in einer
Konfigurationsdatei. Die Verarbeitung der Wetter- und Kartendaten zu einem Flash-Film
wird durch ein Programm erfolgen, dass aufgrund einer vorher festgelegten Konfiguration
ohne weitere Benutzersteuerung ein Resultat in Dateiform liefert. Nur so kann
ein Prozess unbeaufsichtigt alle 24 GRIB-Dateien für eine 24-Stunden-Vorhersage
bearbeiten.
Da die Konfiguration des Visualisierungsprozesses recht umfangreich werden kann,
erfolgt die Konfiguration in Konfigurationsdateien, deren Inhalt Anweisungen
im XML-Format enthält und von einer DTD bestimmt wird. Das hat den Vorteil,
dass fehlende obligatorische Elemente oder falsch benannte Parameter schon beim
Einlesen dem XML-Parser auffallen. Die Korrektheit der Werte für die einzelnen
Parameter (z.B. Gleitkommzahl größer Null, Parameter x größer Parameter y) kann
jedoch erst zur Laufzeit erfolgen 16.
Die Klasse ConfigDocument im Paket de.flashweather.io.util
ermöglicht das Abfragen der Elemente einer Konfigurationsdatei mit folgendem
Aufbau:
Azimuthale Projektion
5.2.2 Java-Klassenhierarchie
5.2.3 Einzelne Java-Klassen
stammt als einzige Klasse dieses Pakets nicht von GraphicObject ab,
da es kein Grafikobjekt sondern die Ausdehnung eines solchen in der Form
eines Rechtecks repräsentiert. Über Methoden lassen sich die minimalen und maximalen
Koordinaten auslesen (getXMin, getYMin, getXMax,
getYMax) und weitere Punkte oder Bounds-Objekte umschließen
(include).
ist die abstrakte Basisklasse aller Grafikobjekte. Sowohl einfache Punkt-Objekte
(Point) als auch komplexe Grafikobjekte (Shape) stammen
von ihr ab und implementieren die abstrakten Methoden, die elementare Transformationen
(translate, scale, rotate, transform) und
die Ausgabe einer XML-Repräsentation (serialize) des Java-Objekts
zur Verfügung stellen.
ist ein 2D-Punkt und das einfachste Grafikobjekt. Objekte dieser Klasse verfügen
über Methoden zum Lesen (getX, getY) und Setzen (setCoordinates)
der Koordinaten, zum Vergleichen des Punkts mit einem anderen (distanceTo,
equals) und Operationen, die Projektionen (getSurfacePoint)
und Clipping (isInsideWindow) unterstützen.
ist ein Point, der die Position eines Labels, d.h. die Bezeichnung
eines Grafikobjekts, markiert.
ist eine Erweiterung der Klasse Point und repräsentiert einen Rasterpunkt.
Die Klasse verfügt daher über zusätzliche Methoden zum Lesen (getValue)
und Setzen (setValue) des zum Rasterpunkt gehörenden Skalarwertes.
stammt von GraphicObject ab und ist die abstrakte Basisklasse aller
komplexen Grafikobjekte. Die einzigen abstrakten Methoden sind cloneHull,
die das Grafikobjekt klont, ohne die Definitionspunkte ebenfalls zu klonen,
und getAllPoints, die alle Definitions- und Labelpunkte liefert. Weitere
Methoden für den Zugriff auf die Definitionspunkte eines komplexen Grafikobjekts
stehen - je nach Typ - in den Subklassen zur Verfügung. Shape selbst
implementiert Methoden zum Verwalten von weiteren Attributen (get-/setAttribute,
read-/writeAttributes) und zusätzlichen Sohnknoten innerhalb der
XML-Struktur (addInfoNode, getInfoNode), außerdem
zum Lesen (getBounds) und Setzen (setBounds, setHeight,
setWidth) der Ausdehnung des Grafikobjekts.
ist das einfachste komplexe Grafikobjekt und beinhaltet einen einzelnen Punkt
(z.B. eine Stadt). Neben den abstrakten Methoden der Superklassen Shape
und GraphicObject implementiert die Klasse die Methoden zum Lesen (getPoint)
und Setzen (setPoint) dieses Point-Objekts.
stammt von Shape ab und gehört daher zu den komplexen Grafikobjekten.
Die Klasse repräsentiert einen Linienzug, der sich über mehrere Punkte erstrecken
kann. Sie implementiert Methoden zum Lesen (getPoint, getPoints,
getFirstPoint, getLastPoint), Setzen (addPoint,
insertPointAt, setPoints) und Löschen (removePoint)
der Definitionspunkte und zusätzlich Methoden zum Verwalten von Punkten, an
deren Position die Linie mit Labels versehen werden soll (addLabelPoint,
getLabelPoint, setLabelPoints). Die Anzahl der Linienpunkte
kann ebenso ermittelt werden (getLength) wie die Existenz eines bestimmten
Punkts auf der Linie (searchPoint).
stammt von LineShape ab, da ein Polygon einen geschlossenen Linienzug
darstellt. Zusätzlich implementiert dieses komplexe Grafikobjekt Methoden
zum Lesen (getFillIndex) und Setzen (setFillIndex) des Füllindex
der Polygonfläche - ein Wert der später in eine Farbe übersetzt werden kann.
repräsentiert ein Raster und implementiert wie alle komplexen Grafikobjekte
die abstrakt definierten Shape- und GraphicObject-Methoden.
Die Rasterpunkte lassen sich auslesen (getGridPoint, getGridPointValue)
und verändern (setGridPoint, setGridPointValue).
Spezielle Methoden liefern die Dimension des Rasters (getXDimension,
getYDimension), verändern die Skalarwerte an allen Rasterpunkten
(changeValues), verschieben die Ränder des Rasters in die Mitte
(foldHorizontal, foldVertical) und erstellen
eine generalisierte Version des Rasters (getGeneralized), in
der jeweils eine bestimmte Anzahl Rasterpunkte zu einem neuen zusammengefasst
ist.
ist eine spezielle Erweiterung von Shape, da diese Klasse sich selbst
wie ein komplexes Grafikobjekt verhält, aber gleichzeitig einen Container
für diese Objekte darstellt und eine beliebige Anzahl von Shape-Objekten
beinhalten kann. ShapeSet implementiert die Methoden der abstrakten
Superklassen Shape und GraphicObject, indem die Aufrufe der
Methoden an jedes einzelne Shape-Objekte weitergereicht werden. Außerdem
stehen Methoden zur Verwaltung der Shapes zur Verfügung (addShape,
getShape, setShapes).
5.2.4 Weitere Eigenschaften
Je nach Klasse erwartet der dritte Konstruktor als Parameter einen Teilbaum
mit der in Abschnitt 5.1.2 festgelegten Struktur
für komplexe Grafikobjekte oder ein einzelnes Punkt- bzw. Rasterpunkt-Tag
mit entsprechenden Attributen. Die korrekte Interpretation der XML-Quelle kann
jedoch nur erfolgen, wenn die Java-Klasse die Namen der verwendeten Tags und
Attribute kennt. Nur anhand dieser Namen kann die Klasse entscheiden, ob und
um welche Eigenschaften des Grafikobjekts es sich handelt. Tabelle 5.1
listet die XML-Merkmale auf, die den Grafikobjekt-Klassen bekannt sein müssen.
Der leere Konstruktor, der ein Grafikobjekt mit leerem Inhalt oder
Standardwerten anlegt.
Ein Konstruktor, der Koordinaten, Definitionspunkte oder Shape-Objekte
bereits als Parameter erhält und damit den Inhalt des Grafikobjekts füllt.
Ein Konstruktor, der einen Knoten eines XML-Strukturbaums als Parameter übergeben
bekommt (ein Element-Objekt) und anhand dieses Teilbaums über die Schnittstellen
des DOM den Inhalt des Grafikobjekts festlegt.
Klasse
bekannte XML-Merkmale
Point
Tagname des Grafikobjekts
Attributnamen der x- und y-Koordinate
LabelPoint
Tagname des Grafikobjekts
Attributnamen der x- und y-Koordinate
GridPoint
Tagname des Grafikobjekts
Attributnamen der x- und y-Koordinate und des Skalarwertes
PointShape
Tagname des Grafikobjekts
Definitionspunkt Point
LineShape
Tagname der Grafikobjekts
Definitions- und Labelpunkte Point, LabelPoint
PolygonShape
Tagname des Grafikobjekts
Attributname des Füllindex
Definitions- und Labelpunkte Point, LabelPoint
GridShape
Tagname des Grafikobjekts
Attributnamen der x- und y-Dimension des Rasters
Rasterpunkte GridPoint
ShapeSet
Tagname des Grafikobjekts
Shapes ...Shape
import de.flashweather.graph2d.base.Point;
import de.flashweather.graph2d.base.PointShape;
import org.apache.xerces.dom.DocumentImpl; // DOM-Implementation
public class XMLOutput {
public static void main( String[] args ) {
PointShape.XML_TAG = "stadt"; // konfiguriere
Point.XML_TAG = "lage"; // Grafikobjekt-
Point.XML_ATTR_X = "laengengrad"; // Klassen
Point.XML_ATTR_Y = "breitengrad";
PointShape p = new PointShape( 8.07, 52.27 ); // neues PointShape
p.setAttribute("name", "Osnabrück"); // ergänze Attribut
Document doc = new DocumentImpl(); // neues XML-Dokument
p.serialize( doc ); // hänge Teilbaum für
} // Grafikobjekt ein
}
<?xml version="1.0" encoding="UTF-8"?>
<stadt name="Osnabrück">
<lage breitengrad="52.27" laengengrad="8.07"/>
</stadt>
5.3 Lesemodule
5.3.1 Shapefile
<!ELEMENT map (part+)>
<!ATTLIST map type (country|river|lake|city) #REQUIRED>
<!ELEMENT part (point+)>
<!ATTLIST part id ID #REQUIRED
type CDATA #REQUIRED
label CDATA ""
category CDATA #IMPLIED>
<!ELEMENT point EMPTY>
<!ATTLIST point x CDATA #REQUIRED
y CDATA #REQUIRED>
<?xml version="1.0"?>
<!DOCTYPE map SYSTEM "map.dtd">
<map type="city">
<part id="city1" type="point" label="Hamburg" category="1">
<point x="9.9748" y="53.5358"/>
</part>
</map>
5.3.2 GRIB
import de.flashweather.io.grib.GribFile;
import de.flashweather.io.grib.GribRecord;
public class GribInfo {
public static void main( String[] args ) {
try {
GribFile grib = new GribFile( args[0] );
// durchlaufe alle Records der GRIB-Datei
for (int rec=1; rec<=grib.getRecordCount(); rec++) {
GribRecord record = grib.getRecord( rec );
System.out.println("Record " + rec );
System.out.println(" Type " + record.getType() + "\n");
System.out.println( record + "\n");
}
}
// Öffnen oder Lesen der GRIB-Datei fehlgeschlagen?
catch ( Exception e ) {
e.printStackTrace();
}
}
}
5.4 Vektorisierung von Rasterdaten
Isowerte
-5.0
0.0
5.0
Wertebereiche
.. -5.0
-5.0 .. 0.0
0.0 .. 5.0
5.0 ..
Füllindezes
0
1
2
3
Markierung der Gitterkanten
Beschriftung der Isolinien
Randpolygone I
Randpolygone II
innere Polygone mit Ausschneiden I
innere Polygone mit Ausschneiden II
innere Polygone ohne Ausschneiden I
innere Polygone ohne Ausschneiden II
Verwendung der Klasse
beeinflusst das Verhalten des Algorithmus beim Auftreten innerer Polygone. Innere
Polygone sind Flächen, die keinen Kontakt mit dem Rand des Rasters haben. Sie
werden von einer geschlossenen Isolinie begrenzt und beinhalten eventuell andere
innere Polygone. Als Flächen betrachtet, liegen innere Polygone zuerst auf
anderen Polygonen.
Der boolesche Parameter cutout der Methode bestimmt, ob innere Polygone
aus der Fläche, auf der sie liegen, herausgeschnitten werden oder nicht. Wird
auf eine spätere Füllung der Flächen wert gelegt, ist der Vorgang zu aktivieren.
Sind die eigentlichen Isolinien von Interesse, ist das Ausschneiden der inneren
Polygone nicht durchzuführen. Siehe Abbildung 4.8.
konfiguriert, ob und wie oft Polygonpunkte als Positionen für Beschriftungen
verwendet werden. Da Beschriftungen auf dem Rand des Rasters nicht sinnvoll
sind, gibt der Parameter boundarydistance an, ab dem wievielten Punkt
im Innern des Rasters (die Polygonlinie entfernt sich vom Rand) eine Beschriftung
im Abstand von distance Punkten erfolgen soll. Ein Wert von 0 für distance
deaktiviert die Beschriftung.
double[] contourvalues )
führt die eigentliche Vektorisierung durch. Entsprechend des rechteckigen Rasters
grid und der Isowerte contourvalues liefert die Methode ein
ShapeSet-Objekt, das die ermittelten Isoflächen als PolygonShapes
enthält.
5.5 Transformation der Java-Grafikobjekte
5.5.1 Integrierte Transformationen
Elementare Transformationen
Tatsächlich implementiert werden die genannten vier Methoden in der Point-Klasse.
LabelPoint und GridPoint erben sie von der Point-Klasse
und komplexe Grafikobjekte führen die Methoden aus, indem sie sie auf jeden
ihrer Definitions-, Raster- und Labelpunkte anwenden.
verschiebt das Objekt entsprechend der Argumente in x- und y-Richtung.
verändert den Abstand jedes Punkts vom Ursprung entsprechend der Skalierungsfaktoren
xscale und yscale.
dreht das Objekt entgegen dem Uhrzeigersinn um angle Grad um den Ursprung
des Koordinatensystems.
gehört nicht unbedingt zu den elementaren Transformationen, sondern
führt eine allgemeine Transformation mit der 3x3-Transformationsmatrix durch,
die durch die Argumente a11 bis a33 zeilenweise definiert
wird.
Generalisierung eines Rasters
5.5.2 Externe Transformationsklassen
Clipping
Keine der genannten Methoden verändert das als Argument übergebene komplexe
Grafikobjekt. clip(PointShape, ...) liefert als einzige das ursprüngliche
Grafikobjekt zurück, sofern es im Clipping-Bereich liegt - da es unverändert
bleibt. Alle anderen Methoden verwenden Kopien des Grafikobjekts für die
Durchführung des Clipping. Diese Eigenschaft der Implementation kann man sich
bei einer Kachelung der ursprünglichen Fläche (vgl. Abschnitt 4.5)
zunutze machen.
clippt ein komplexes Grafikobjekt an einem rechteckigen Fenster. Da Shape
eine abstrakte Klasse ist, ermittelt diese Methode den tatsächlichen Typ von
shape (eine Subklasse von Shape) und ruft die passende clip-Methode
auf.
gibt das Argument point wieder zurück, falls der Punkt im Innern des
Clipping-Fensters liegt, null sonst.
clippt eine Linie, die sich über mehrere Punkte erstrecken kann, an einem rechteckigen
Fenster. Diese Methode liefert als einzige nicht den Objekttyp des übergebenen
Grafikobjekts zurück, da die Linie beim Clipping in mehr als ein Teilstück
zerfallen kann. Deshalb liefert die Methode alle Teilstücke als LineShape-Objekte
in einem ShapeSet zurück.
clippt ein Polygon an einem rechteckigen Fenster nach dem Algorithmus von Sutherland
und Hodgeman [SuHo1974].
löscht alle Zeilen und Spalten des Rasters, die komplett außerhalb des Clipping-Fensters
liegen - bis auf die jeweiligen (nicht-sichtbaren) Zeilen und Spalten direkt
am Rand des Fensters, die noch bei einer Vektorisierung der Rasterdaten benötigt
werden. Die Methode liefert das Resultat als neues GridShape-Objekt
zurück.
ruft für alle Elemente des ShapeSet die entsprechende clip-Methode
auf.
Projektion
Das von Projection abstammende Interface AzimuthalProjection
spezifiziert eine azimuthale Projektion, wie sie in Abschnitt 4.3
vorgestellt wurde. Die zusätzliche Methode setReferencePoint(double
x, double y) setzt den Berührpunkt der Projektionsebene an die Kugel auf die
Koordinaten x,y. Die Entwurfsachse der Projektion verläuft
damit durch diesen Punkt und den Mittelpunkt der Kugel.
wendet die Projektion auf einen Punkt an, der auch ein LabelPoint oder
GridPoint sein kann.
wendet die Projektion auf alle Definitions-, Label- und Rasterpunkte eines komplexen
Grafikobjekts an.
5.5.3 Transformationen im Überblick
5.6 Konfiguration und Übersetzung
5.6.1 Konfigurationsdatei
<config>
Beginn der Konfiguration
< parameter attr1="value1"
Parameter mit Attribut
attr2="value2"
zweites Attribut
... />
weitere Attribute
...
weitere Parameter
</config>
Ende der Konfiguration
Für die bisher implementierten Transformationen wurden Parameternamen gewählt
und die benötigten Einstellungen in Attributen abgelegt. Je Transformation entsteht
so ein XML-Tag, das in der Konfigurationsdatei verwendet wird und angibt, welche
Transformation wann und mit welchen Einstellungen ausgeführt werden soll. Für
die einzelnen Transformationen ergibt sich im Einzelnen:
liefert true, falls noch weitere Elemente folgen.
liefert die nächste Konfigurationsanweisung als DOM-Element und macht es zum
aktuellen Element. Die Methoden getNodeName und getAttribute
des DOM-Interface Element liefern dann den Namen des Parameters und
die Werte der Attribute.
springt an den Anfang der Konfigurationsdatei zurück.
Die Attribute geben die Anzahl der Rasterpunkte je Zeile und Spalte an, die
zu einem einzigen Rasterpunkt zusammengefasst werden. Die Standardwerte sind
jeweils 1.
<generalize row="Anzahl der Zeilen"
col="Anzahl der Spalten"/>
Zu jedem Parameterwert eines Rasterpunkts wird zuerst add addiert,
bevor das Resultat mit mult multipliziert wird.
<changevalues add="Summand"
mult="Faktor"/>
Die angegebene Klasse muss das Interface Vectorizer implementieren.
Die Isowerte sind beim Attribut isovalues durch Semikolon oder Leerzeichen
zu trennen. Standardwert für cutoutinternalpolygons ist true,
für boundarydistance und labeldistance ist dieser 0 (Beschriftung
ist deaktiviert).
<vectorize class="voller Name der Vektorisiererklasse"
isovalues="Liste von Isowerten"
cutoutinternalpolygons="true|false"
boundarydistance="Index eines Polygonpunkts"
labeldistance="Anzahl Punkte zwischen Labeln"/>
<project class="voller Name der Projektionsklasse"
type="azimuthal"
refx="x-Koordinate des Referenzpunkts"
refy="y-Koordinate des Referenzpunkts"/>
<project class="voller Name der Projektionsklasse"
type="rotategrid"
rotaxis1="x|y|z erste Rotationsachse"
rotangle1="Rotationswinkel in Grad"
rotaxis2="x|y|z zweite Rotationsachse"
rotangle2="Rotationswinkel in Grad"
rotaxis3="x|y|z dritte Rotationsachse"
rotangle3="Rotationswinkel in Grad"/>
Die angegebene Klasse muss das Interface Clipper implementieren.
<clip class="voller Name der Clipping-Klasse"
xmin="minimale x-Koordinate des Clipping-Fensters"
ymin="minimale y-Koordinate des Clipping-Fensters"
xmax="maximale x-Koordinate des Clipping-Fensters"
ymax="maximale y-Koordinate des Clipping-Fensters"/>
<translate xtranslate="Verschiebung in x-Richtung"
ytranslate="Verschiebung in y-Richtung"/>
<scale xscale="Skalierungsfaktor in x-Richtung"
yscale="Skalierungsfaktor in y-Richtung"/>
<rotate angle="Rotationswinkel in Grad"/>
<transform a11="Element 1,1 der Transformationsmatrix"
a12="Element 1,2 der Transformationsmatrix"
a13="Element 1,3 der Transformationsmatrix"
a21="Element 2,1 der Transformationsmatrix"
...
a33="Element 3,3 der Transformationsmatrix"/>
<setbounds xmin="minimale x-Koordinate der Ausdehnung"
ymin="minimale y-Koordinate der Ausdehnung"
xmax="maximale x-Koordinate der Ausdehnung"
ymax="maximale y-Koordinate der Ausdehnung"/>
<setheight height="Höhe der Ausdehnung aller Grafikobjekte"/>
<setwidth width="Breite der Ausdehnung aller Grafikobjekte"/>
Bei der Erstellung einer Konfigurationsdatei ist darauf zu achten, dass z.B. ein generalize-, changevalues- oder vectorize-Aufruf nur für Rasterdaten erfolgen kann und vectorize die Schnittstelle zwischen Raster- und Vektordaten bildet.
Wer aber verwaltet tatsächlich die Java-Grafikobjekte und startet die Transformationen,
die die Konfigurationsdatei vorschreibt?
Die abstrakte Klasse BaseShapeDocument im Paket de.flashweather.io.shape ist die Basisklasse aller Klassen, die eine Datei repräsentieren, die in XML codierte Grafikobjekte enthält. Sie bildet die Schnittstelle zwischen verwendeter XML-Ausprägung und Java-Grafikobjekten bzw. Transformationen.
Abbildung 5.9 veranschaulicht die Arbeitsweise von
BaseShapeDocument und einer Subklasse, die die noch abstrakten Methoden
aus BaseShapeDocument für eine konkrete Anwendung implementiert.
BaseShapeDocument bietet eine Methode read zum Einlesen einer
XML-Datei, die üblicherweise innerhalb eines Konstruktors aufgerufen wird. read
startet einen DOM-Parser, der aus dem Inhalt der XML-Datei den korrespondierenden
Dokumentobjektbaum erzeugt und ein Dokumentobjekt zurückliefert.
Über das Dokumentobjekt vom Typ Document und den Schnittstellen des
DOM können alle Baumelemente erreicht werden.
Der Dokumentobjektbaum enthält die Grafikobjekte aus der XML-Datei in Textform.
Das tatsächliche Umsetzen der Baumobjekte in Java-Grafikobjekte muss in der
Methode buildShapes außerhalb der Klasse BaseShapeDocument
für jede XML-Ausprägung individuell erfolgen. Die Java-Grafikobjekte unterstützen
das Einlesen und die Interpretation von XML-Teilbäumen, die Struktur und vor
allem die Tag- und Attributnamen können jedoch variieren. buildShapes
ist daher in BaseShapeDocument eine abstrakte Methode.
Das Kernstück der Klasse BaseShapeDocument ist die Methode modify.
Sie erhält eine Konfigurationsdatei in Form eines ConfigDocument-Objekts
und verarbeitet die Anweisungen zu Transformationen, die auf den Java-Grafikobjekten
ausgeführt werden. Quellcode 5.4 zeigt einen Ausschnitt
aus der Methode, in der für eine clip-Anweisung in der Konfigurationsdatei
die über das Attribut class angegebene Klasse geladen wird und das
Clipping durchgeführt wird. Hier wird klar, warum alle externen Transformationsklassen
nur leere Konstruktoren besitzen: Class.newInstance() führt den
leeren Konstruktor der durch Class referenzierten Klasse aus.
Quellcode 5.4: Methode
Sind alle Transformationen durchgeführt, folgt die Methode buildDocument,
die ein Pendant zur Methode buildShapes darstellt. Sie wandelt die
Java-Grafikobjekte wieder in einen XML-Objektbaum um und kann deshalb für eine
konkrete Anwendung ebenfalls erst in einer Subklasse implementiert werden.
Abschließend ist die Methode write für die Ausgabe des erzeugten Dokumentobjektbaums
zuständig. write kann neben dem Ausgabestrom als zweites Argument ein
XSLT-Stylesheet erhalten. Dies führt dazu, dass noch vor der Ausgabe ein XSLT-Prozessor
den Inhalt und die Struktur des Objektbaums anhand der Anweisungen des Stylesheets
verändert. Der Ergebnisbaum wird dann im XML-Format in den Ausgabestrom geschrieben.
Nach der Transformation der Grafikobjekte ist also eine Transformation der verwendeten
XML-Ausprägung ebenfalls möglich. Von BaseShapeDocument abstammende
Klassen können so aus
Der Flash-Generator Saxess Wave, der im Rahmen dieser Arbeit zur Erzeugung der
Flash-Filme verwendet wird, wurde in Kapitel 3.7 vorgestellt.
Ebenfalls wurde bereits erwähnt, dass sich ein Flash-Film aus einer SWFML-Datei
mit dem Kommandozeilenaufruf
java com.saxess.visweb.swfio.Driver in.swfml out.swf
erzeugen lässt. Eine einzelne SWFML-Datei bestimmt also den Inhalt eines Flash-Films.
Vom Deutschen Wetterdienst stehen allerdings je Tag 24 GRIB-Dateien mit Stundenprognosen
zur Verfügung, die in einem animierten Wetterfilm nacheinander angezeigt werden
sollen. Da aber aus jeder einzelnen GRIB-Datei schließlich eine SWFML-Datei
entsteht, muss aus allen 24 SWFML-Dateien der Wetterfilm zusammengestellt werden.
Das ist möglich, indem die SWFML-Dateien wie in Abbildung 5.10
dargestellt über die resultierenden Dokumentobjektbäume zu einem einzigen Objektbaum
bzw. einer einzigen Datei verschmolzen werden, die dann dem Flash Generator
übergeben wird. Dabei ist es nicht einmal nötig, den Objektbaum aller SWFML-Dateien
tatsächlich wieder in eine Datei zu schreiben, da die Klasse Driver
von Saxess Wave daraus über folgende Java-Aufrufe direkt einen Flash-Film erzeugt:
java Swfml2Swf in.swfml [in.swfml ...] out.swf
package de.flashweather.io.shape;
import ...
public abstract class BaseShapeDocument {
protected Shape shape;
...
public void modify( ConfigDocument config ) throws ... {
while ( config.hasNextElement() ) {
// get line from configuration file
Element confline = config.getNextElement();
String category = confline.getNodeName();
if ( category.compareTo("clip") == 0 ) {
// instantiate clipping class
String classname = confline.getAttribute("class");
Class clipclass = Class.forName( classname );
Clipper clipper = (Clipper) clipclass.newInstance();
// get attribute values as doubles
double xmin = XMLUtil.parseInt( confline, "xmin");
double ymin = XMLUtil.parseInt( confline, "ymin");
double xmax = XMLUtil.parseInt( confline, "xmax");
double ymax = XMLUtil.parseInt( confline, "ymax");
// wrong arguments?
if ( xmin > xmax || ymin > ymax )
throw new IllegalArgumentException("Wrong clip window" +
" definition: xmin > xmax || ymin > ymax");
// clip
this.shape = clipper.clip(this.shape,xmin,ymin,xmax,ymax);
continue;
}
...
}
}
...
}
modify
der Klasse BaseShapeDocument
in einem Durchgang die für einen Flash-Film benötigte SWFML-Datei erstellen.
5.7 Erzeugung von Flash-Filmen
Die selbst geschriebene Klasse, die eine beliebige Anzahl von SWFML-Dateien
in einen einzigen Objektbaum einhängt und daraus mit Saxess Wave einen Flash-Film
generiert, heißt Swfml2Swf. Ihr Aufruf lautet:
import com.saxess.visweb.swfio.Driver;
...
Document doc = Dokumentobjektbaum aller SWFML-Dateien;
Driver swfdriver = new Driver( doc );
swfdriver.parse( Dateiname des Flash-Films );
Fußnoten
Nächste Seite: 6. Anwendung
Aufwärts: Diplomarbeit
Vorherige Seite: 4. Daten und
Transformationen   Inhalt
Benjamin Stark
2001-02-14