next up previous contents
Nächste Seite: 6. Anwendung Aufwärts: Diplomarbeit Vorherige Seite: 4. Daten und Transformationen   Inhalt

Unterabschnitte

5. Realisierung

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.

5.1 Datenfluss und Datenformate

5.1.1 Vollständiger Datenfluss

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.

Abbildung 5.1: Vollständiger Datenfluss

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.


5.1.2 Allgemeines Datenformat

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

In Anlehnung an das SWFML-Format, wurde für die Speicherung von Grafikobjekten die folgende XML-Struktur entworfen:
< 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
Ein äußeres Tag repräsentiert das Grafikobjekt, indem es die Attribute desselben besitzt und dessen Definitionspunkte und weitere Informationen als untergeordnete Tags umschließt. Alle Tags und Attributnamen sollen jedoch im Gegensatz zum SWFML-Polygon frei wählbar sein. Nur so können unterschiedliche Datenformate mit semantisch benannten Tags verwendet werden, wie XML es vorsieht (z.B. <river> und <city> in einem Format für Landkarten).

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.

5.2 Grafikobjekte in Java

5.2.1 Grafikobjekte

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"/>

Komplexe Grafikobjekte verwenden diese einfachen Punkt-Objekte für die Speicherung ihrer Definitionspunkte. Ihnen ist jedoch erlaubt, neben den eigentlichen Positions-Informationen weitere Attribute zu beherbergen, die keinen direkten Bezug zur Definition der grafischen Form haben. Komplexe Grafikobjekte entsprechen damit einem Ausdruck wie er im vorherigen Abschnitt als Entwurf für die Speicherung von Grafikobjekten in XML vorgestellt wurde. Beliebige Attribute des Start-Tags und zusätzliche Sohn-Knoten werden während einer Transformation des Grafikobjekts zwischengespeichert und schließlich bei einer Generierung des entsprechenden XML-Codes wieder mit ausgegeben.

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.

Abbildung 5.2: Grafikobjekte in XML und Java


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.

5.2.2 Java-Klassenhierarchie

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.

Abbildung 5.3: Klassenhierarchie der Java-Grafikobjekte

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.

5.2.3 Einzelne Java-Klassen

Die Grafikobjekt-Klassen des Java-Pakets de.flashweather.graph2d.base werden nun im Einzelnen vorgestellt:

5.2.4 Weitere Eigenschaften

Alle von GraphicObject abstammenden, nicht-abstrakten Klassen verfügen über mindestens drei Konstruktoren:

  1. Klassenname()
    Der leere Konstruktor, der ein Grafikobjekt mit leerem Inhalt oder Standardwerten anlegt.
  2. Klassenname( unterschiedliche Parameter )
    Ein Konstruktor, der Koordinaten, Definitionspunkte oder Shape-Objekte bereits als Parameter erhält und damit den Inhalt des Grafikobjekts füllt.
  3. Klassenname( Element )
    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.
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.


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

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.

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
}

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:

<?xml version="1.0" encoding="UTF-8"?>
<stadt name="Osnabrück">
    <lage breitengrad="52.27" laengengrad="8.07"/>
</stadt>
Wird also ein neues Grafikobjekt erzeugt und sein Inhalt mit Hilfe von Java-Anweisungen erstellt, dienen die Werte der Klassenvariablen bei der Initialisierung zur Festlegung der XML-Tags und -Attribute für eine spätere XML-Ausgabe 15.

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

5.3 Lesemodule


5.3.1 Shapefile

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.

 <!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>

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

<?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

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: GRIB-Datei in Java

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.

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();
    }
  }
}

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.

5.4 Vektorisierung von Rasterdaten

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.


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

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.

Markierung der Gitterkanten

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.

Beschriftung der Isolinien

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.

Abbildung 5.5: 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

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:

5.5 Transformation der Java-Grafikobjekte

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.

5.5.1 Integrierte Transformationen

Elementare Transformationen

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:

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.

Generalisierung eines Rasters

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.

Abbildung 5.6: Generalisierung eines rechteckigen Rasters

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.

5.5.2 Externe Transformationsklassen

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.

Clipping

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

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.

Projektion

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.

Abbildung 5.7: Klassenhierarchie der Projektions-Klassen und -Interfaces

Das Interface Projection definiert für alle Projektionsklassen die beiden Methoden:

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.

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.

5.5.3 Transformationen im Überblick

Abbildung 5.8 zeigt noch einmal alle Transformationen und Transformationsklassen im Überblick.

Abbildung 5.8: Transformationen und Transformationsklassen


5.6 Konfiguration und Übersetzung

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.

5.6.1 Konfigurationsdatei

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:

<config>                                Beginn der Konfiguration
     < parameter attr1="value1"         Parameter mit Attribut
                 attr2="value2"         zweites Attribut
                ...             />      weitere Attribute
     ...                                 weitere Parameter
</config>                               Ende der Konfiguration
Alle dem einleitenden Tag config untergeordneten Elemente sind leere Tags, die außer Attributen keinen Inhalt besitzen. Zum Auslesen des Inhalts einer solchen Konfigurationsdatei verfügt die Klasse ConfigDocument über folgende Methoden: 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:

  1. Generalisierung
     
    <generalize row="Anzahl der Zeilen
                col="Anzahl der Spalten"/>
     
    Die Attribute geben die Anzahl der Rasterpunkte je Zeile und Spalte an, die zu einem einzigen Rasterpunkt zusammengefasst werden. Die Standardwerte sind jeweils 1.

  2. Veränderung der Parameterwerte des Rasters
     
    <changevalues add="Summand
                  mult="Faktor"/>
     
    Zu jedem Parameterwert eines Rasterpunkts wird zuerst add addiert, bevor das Resultat mit mult multipliziert wird.

  3. Vektorisierung
     
    <vectorize class="voller Name der Vektorisiererklasse
               isovalues="Liste von Isowerten
               cutoutinternalpolygons="true|false" 
               boundarydistance="Index eines Polygonpunkts
               labeldistance="Anzahl Punkte zwischen Labeln"/>
     
    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).

  4. Projektion

    Azimuthale Projektion
     

    <project class="voller Name der Projektionsklasse"  
             type="azimuthal" 
             refx="x-Koordinate des Referenzpunkts
             refy="y-Koordinate des Referenzpunkts"/>
     
    Drehung des Gradnetzes

    <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"/>
     
    Es existieren zwei unterschiedliche Projektionen, die sich in den verwendeten Attributen unterscheiden. Das Wert des Attributs type gibt an, um welchen Projektionstyp es sich handelt. Die angegebene Klasse muss das Interface AzimuthalProjection bzw. RotateGridProjection implementieren. Standardwert für die Koordinaten des Referenzpunkts und die Rotationswinkel ist 0.

  5. Clipping
     
    <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"/>
     
    Die angegebene Klasse muss das Interface Clipper implementieren.

  6. Translation
     
    <translate xtranslate="Verschiebung in x-Richtung
               ytranslate="Verschiebung in y-Richtung"/>
    Die Standardwerte sind 0.

  7. Skalierung
     
    <scale xscale="Skalierungsfaktor in x-Richtung
           yscale="Skalierungsfaktor in y-Richtung"/>
     
    Die Standardwerte sind 1.

  8. Rotation
     
    <rotate angle="Rotationswinkel in Grad"/>
     
  9. Allgemeine Transformation
     
    <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"/>
     
    Transformiert das Grafikobjekt anhand der gegebenen 3x3 Transformationsmatrix. Die Standardwerte ergeben eine Einheitsmatrix.

  10. Ausdehnung
     
    <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"/>
     
    Die Gesamtheit aller Grafikobjekte wird so verschoben und skaliert, dass sie das durch die Koordinaten definerte rechteckige Fenster ausfüllt.

  11. Höhe
     
    <setheight height="Höhe der Ausdehnung aller Grafikobjekte"/>
     
    Skaliert und verschiebt alle Grafikobjekte so, dass ihre gesamte Ausdehnung eine Höhe von height hat und im Ursprung beginnt.

  12. Breite
      
    <setwidth width="Breite der Ausdehnung aller Grafikobjekte"/>
     
    Skaliert und verschiebt alle Grafikobjekte so, dass ihre gesamte Ausdehnung eine Breite von width hat und im Ursprung beginnt.

Die DTD config.dtd der Konfigurationsdateien ist im Anhang C vollständig aufgelistet.

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?

5.6.2 XML, Grafikobjekte und Transformationen

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.

Abbildung 5.9: Die Arbeitsweise von BaseShapeDocument

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.

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;
         }
  
         ...
      }
   }
    
   ...
}

Quellcode 5.4: Methode modify der Klasse BaseShapeDocument

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

in einem Durchgang die für einen Flash-Film benötigte SWFML-Datei erstellen.


5.7 Erzeugung von Flash-Filmen

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.

Abbildung 5.10: Verschmelzen von SWFML-Dateien

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:

import com.saxess.visweb.swfio.Driver; 
... 
Document doc =  Dokumentobjektbaum aller SWFML-Dateien; 
Driver swfdriver = new Driver( doc ); 
swfdriver.parse( Dateiname des Flash-Films );
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:

     java Swfml2Swf in.swfml [in.swfml ...] out.swf



Fußnoten

... XML-Ausgabe15
Dieser Vorgang ist nur dann thread-safe, wenn zwischen dem Setzen der Klassenvariablen und dem Erzeugen der Grafikobjekte kein anderer Thread die Werte der Klassenvariablen verändern kann. Das wird z.B. durch einen synchronized-Block bzgl. der verwendeten Klassen verhindert, sollten tatsächlich mehrere Threads existieren.
... erfolgen16
Erst in XML Schema sind Datentypen und Wertebereichsprüfungen vorgesehen.

next up previous contents
Nächste Seite: 6. Anwendung Aufwärts: Diplomarbeit Vorherige Seite: 4. Daten und Transformationen   Inhalt
Benjamin Stark
2001-02-14