Die bisher möglichst allgemein gehaltene Implementation der Java-Grafikobjekte und der Transformationsklassen bildet eine Bibliothek von Java-Klassen zur Bearbeitung von in XML codierten zweidimensionalen Grafikobjekten. Diese FlashWeather-Bibliothek soll nun zur Erzeugung von Flash-Filmen aus den Wettervorhersagedaten des Deutschen Wetterdienstes angewendet werden.
Die Erzeugung der Flash-Filme ist wenig prozessor- aber sehr speicherintensiv, wie die Testläufe in Abschnitt 6.4 zeigen werden. Dabei bestimmen der Umfang der Eingabedaten und die Konfiguration des Visualisierungsvorgangs, wieviel Speicher tatsächlich benötigt wird: je höher die Auflösung der Daten, desto höher der Speicherbedarf.
Neben den Java-Grafikobjekten ist vor allem die verwendete DOM-Technologie, die XML-Dateien als Objektbäume im Speicher hält, für den hohen Bedarf an Speicher verantwortlich. Denn jeder Punkt, der in einer XML-Datei als Tag definiert ist, erweitert den entsprechenden Dokumentobjektbaum um drei Node-Objekte (Punkt, x-Attribut, y-Attribut).
Für die Erzeugung eines 170 KB großen Flash-Films, der für ein Deutschland umfassendes Gebiet für jede volle Stunde eines Tages die Wetterdaten und im Hintergrund die Landesgrenze, Flüsse, Seen und Städte zeigt, werden 50 MB Arbeitsspeicher benötigt.
Da Sun Microsystems das Java Development Kit (JDK) für Linux, Solaris und Windows implementiert und es auf fast alle anderen Plattformen portiert wurde, ist kein spezielles Betriebssystem erforderlich. Die Verwendung der Java2D-API durch Saxess Wave setzt allerdings die Existenz einer grafischen Oberfläche (X11, Windows) voraus.
Folgende Software-Pakete werden für die Erzeugung von Flash-Filmen mit den FlashWeather-Klassen benötigt und sind kostenlos im Internet verfügbar (siehe Anhang A):
Für die Umwandlung von ESRI Shapefiles in MapML-Dateien werden zusätzlich benötigt:
Der Einstieg in die Verwendung der FlashWeather-Klassen erfolgt über die Klasse BaseShapeDocument, die die Schnittstelle zwischen XML-Dokument und Java-Grafikobjekten herstellt (vgl. Abbildung 5.9). Je XML-Dokumenttyp ist eine individuelle Subklasse von BaseShapeDocument zu bilden, die die bisher abstrakten Methoden buildShapes und buildDocument implementiert.
Das Ausgabeformat des Lesemoduls für ESRI Shapefiles wurde MapML genannt, da es die Elemente einer Landkarte (Städte, Flüsse, Seen und Länderumrisse) in einer XML-Ausprägung speichert. Die Subklasse von BaseShapeDocument, die dieses XML-Format in Java-Grafikobjekte überführt und umgekehrt die Grafikobjekte wieder in einen Dokumentobjektbaum verwandelt, heißt deshalb MapDocument.
MapDocument stellt drei Konstruktoren zur Verfügung, die alle das Ziel haben, aus einer XML-Quelle die entsprechenden Java-Grafikobjekte zu erzeugen. Ein String wird als URI (Uniform Resource Identifier) interpretiert, ein InputSource-Objekt als Eingabestrom behandelt und ein Dokumentobjekt direkt verarbeitet.
import de.flashweather.io.shape.BaseShapeDocument; import ...
public class MapDocument extends BaseShapeDocument {
public MapDocument( String uri ) throws ... { this( new InputSource( uri ) ); }
public MapDocument( InputSource in ) throws ... { this( this.read( in ) ); }
public MapDocument( Document doc ) { this.shape = this.buildShapes( doc ); }
Die Methode buildShapes in Quellcode 6.1 geht deshalb davon aus, dass das Argument ein gültiges Dokument liefert und überprüft nur noch den Namen des Dokumenttyps auf ,,map``. Die weiteren Anweisungen setzen nun die in der MapML-DTD definierte Struktur des Dokuments voraus. (Insofern sollte der dritte Konstruktor stets nur mit einem Dokumentobjektbaum als Argument aufgerufen werden, den ein validierender XML-Parser erstellt hat.)
protected Shape buildShapes( Document doc ) throws DOMException { String docname = doc.getDoctype().getName(); if ( docname.compareTo("map") != 0 ) // check doctype throw new DOMException( ... ); ShapeSet.XML_TAG = "map"; // start new set of shapes ShapeSet shapeset = new ShapeSet(); Element root = doc.getDocumentElement(); // get root element String maptype = root.getAttribute("type"); // store maptype attribute shapeset.setAttribute("type", maptype ); NodeList pnodes = root.getChildNodes(); // process all child nodes for (int i=0; i<pnodes.getLength(); i++) { // of "map" root element Node pnode = pnodes.item( i ); // found a "part" element? if ( ( pnode.getNodeType() != Node.ELEMENT_NODE ) || ( pnode.getNodeName().compareTo("part") != 0 ) ) { shapeset.addInfoNode( pnode ); continue; } // found a "part" element Element shapeelement = (Element) pnode; // -> build shape String type = shapeelement.getAttribute("type"); if ( type.compareTo("point") == 0 ) shapeset.addShape( new PointShape( shapeelement ) ); else if ( type.compareTo("line") == 0 ) shapeset.addShape( new LineShape( shapeelement ) ); else if ( type.compareTo("polygon") == 0 ) shapeset.addShape( new PolygonShape( shapeelement ) ); } return shapeset; }Quellcode 6.1: Methode
buildShapes
der KlasseMapDocument
protected Document buildDocument( Shape shape ) { DocumentImpl doc = new DocumentImpl(); // create new document DocumentType doctype = doc.createDocumentType("MapML",null,"map.dtd"); doc.appendChild( doctype ); // append doctype element shape.serialize( doc ); // serialize shapes return doc; }Quellcode 6.2: Methode
buildDocument
der KlasseMapDocument
Im weiteren Verlauf erzeugt die Methode ein ShapeSet-Objekt und verarbeitet alle Elemente des MapML-Dokuments. Jedes part-Element wird entsprechend dem Wert seines Attributs ,,type`` als PointShape-, LineShape- oder PolygonShape-Objekt in den ShapeSet eingefügt. Alle Tags, die nicht den Namen ,,part`` haben, werden als Info-Knoten für die spätere Ausgabe aufbewahrt.
Das Gegenstück, die Umwandlung der Shape-Objekte in einen MapML-Objektbaum, implementiert die Methode buildDocument in Quellcode 6.2. Sie verwendet die Klasse DocumentImpl (Apaches Implementation des Document-Interface) als neues Dokumentobjekt, definiert den Dokumenttyp des Dokuments und ruft die serialize-Methode des ShapeSet-Objekts auf. Da der ShapeSet alle Grafikobjekte bzw. Kartenelemente enthält und als Container die serialize-Methode an seine Elemente weiterreicht, erstellen die Java-Grafikobjekte auf diese Weise selbstständig den Dokumentobjektbaum.
Eine zweite Subklasse von BaseShapeDocument wird für die Wetterdaten des DWD benötigt. Da es sich um Wetterprognosedaten handelt, heißt sie WeatherDocument.
Die Implementation der bisher abstrakten Methoden buildShapes und buildDocument ist analog zur Klasse MapDocument durchzuführen. Einen gravierenden Unterschied zwischen beiden Klassen stellt lediglich das Ausgangsformat der Eingabedaten dar. Die Kartendaten können von MapDocument nur verarbeitet werden, wenn sie im MapML-Format vorliegen. Die Wetterprognosedaten des DWD befinden sich im Gegensatz dazu in GRIB-Dateien, die zwar über das in Abschnitt 5.3.2 vorgestellte Lesemodul ausgelesen werden können, jedoch anschließend durch ein GribRecord- bzw. GridShape-Objekt repräsentiert werden.
Dieser Unterschied hat zur Folge, dass in der Klasse WeatherDocument ein weiterer Konstrukor implementiert werden muss, der die Eingabedaten in Form eines GribRecord-Objekts erhält. Quellcode 6.3 zeigt den neuen Konstruktor, der die eigentlichen Rasterdaten ermittelt und das GridShape-Objekt mit weiteren Attributen versieht.
public WeatherDocument( GribRecord grib ) { // configure GridPoint classes GridPoint.XML_TAG = "point"; // get grid of weather data as GridShape object this.shape = grib.getGridShape(); // set attributes of GridShape this.shape.setAttribute("type", grib.getType() ); this.shape.setAttribute("label", grib.getDescription() ); this.shape.setAttribute("unit", grib.getUnit() ); this.shape.setAttribute("level", grib.getLevel() ); // add date and time as infonodes to GridShape this.shape.addInfoNode( ... ); this.shape.addInfoNode( ... ); }Quellcode 6.3: Konstruktor in der Klasse
WeatherDocument
Um sicherzustellen, dass vor einem Aufruf der write-Methoden die Rasterdaten in Vektordaten umgewandelt wurden, wird außerdem die Methode modify aus BaseShapeDocument überschrieben (siehe Quellcode 6.4). Die neue Methode ruft die ursprüngliche modify-Methode der Superklasse auf und überprüft anschließend den Resultatwert.
public void modify( ConfigDocument config ) throws ... { // configure two Shape classes PolygonShape.XML_TAG = "area"; ShapeSet.XML_TAG = "weather"; // call metod of super class super.modify( config ); // did not get a result shape? if ( this.shape == null ) throw new IllegalStateException( ... ); // did not get a ShapeSet as result? // -> configuration is incorrect (<vectorize> missing?) if ( ! ( this.shape instanceof ShapeSet ) ) throw new IllegalStateException( ... ); // add id attributes to shapes ShapeSet shapeset = (ShapeSet) this.shape; String shapesetid = shapeset.getAttribute("id"); for (int i=0; i<shapeset.getLength(); i++) shapeset.getShape(i).setAttribute("id", shapesetid + "area" + (i+1)); }Quellcode 6.4: Methode
modify
der KlasseWeatherDocument
Eine Vektorisierung der Rasterdaten ist notwendig, um eine Ausgabe der Wetterdaten im folgenden XML-Format zu ermöglichen, das den Namen WeatherML erhielt und die ermittelten Isoflächen speichert:
<weather id="..."
Wetter-Dokument
type="..."
Typ/Code des Parameters
label="..."
Name des Parameters
level="..."
Höhen- oder Druckangabe
unit="...">
Name der Größeneinheit
<date ... />
Datum (Infoknoten)
<time ... />
Zeit (Infoknoten)
<area id="..."
eine Isofläche
label="..."
Beschriftung der Umrisslinie
fill="...">
Füllindex
<point x=".." y=".." />
Punkt des Umrisses
...
weitere Umrisspunkte
<labelpoint x=".." y=".." />
Punkt für eine Beschriftung
...
weitere Labelpunkte
</area>
Ende der Isofläche
...
weitere Isoflächen </weather>
Ende des Wetter-Dokuments
Nachdem nun zwei spezielle Subklassen von BaseShapeDocument zur Verfügung stehen, die für die Karten- und Wetterdaten die konkrete Schnittstelle zwischen XML-Datei und Java-Grafikobjekten bilden, fehlen für die Erzeugung der Wetterfilme nur noch wenige Komponenten: Für Karten- und Wetterdaten werden jeweils
Wetterdaten
Die Konfigurationsdatei für die Transformationen der DWD-Wetterprognosedaten enthält die folgenden Angaben:
<generalize cols="3" rows="3" />
<project class="de.flashweather.graph2d.project.RotateGeoLLGrid" type="rotategrid" rotaxis1="y" rotangle1="-57.5" rotaxis2="z" rotangle2="-10.0" />
Die Positionen der Rasterpunkte sind ursprünglich bezüglich eines rotierten Längen- und Breitengradnetzes definiert und werden in diesem Schritt zu geografischen Koordinaten konvertiert.
<project class="de.flashweather.graph2d.project.Geo2Lambert" type="azimuthal" refx="10.0" refy="50.0" />
<scale xscale="+4200.0" yscale="-4200.0" />
<translate xtranslate="380" ytranslate="386" />
Die Werte für Skalierung und Translation der Grafikobjekte können berechnet werden, werden aber am einfachsten durch Ausprobieren anhand der Hintergrundelemente ermittelt. Ist z.B. der Umriss von Deutschland korrekt in der Mitte des Flash-Films platziert worden, können bzw. müssen die Werte für die Wetterdaten übernommen werden.
<clip class="de.flashweather.graph2d.clip.ClipperImpl" xmin="0" xmax="800" ymin="0" ymax="600" />
<vectorize class="de.flashweather.graph2d.vectorize.VectorizerImpl" isovalues="0.1 0.3 0.5 0.7 0.9" cutoutinternalpolygons="true" />
Das Beispiel stammt aus der Konfigurationsdatei für die Bewölkungsvorhersage, die den Grad der Bedeckung in Prozent angibt. Beschriftungspunkte werden nicht ermittelt.
<clip class="de.flashweather.graph2d.clip.ClipperImpl" xmin="0" xmax="800" ymin="0" ymax="600" />
Die entsprechende Konfigurationsdatei für die Transformation der vorliegenden Kartendaten enthält nur die Punkte 3, 4, 5 und eventell Punkt 8. Eine Generalisierung (Punkt 1) ist genauso wie eine Vektorisierung (Punkt 7) nicht möglich, da die MapML-Daten bereits Vektorgrafikobjekte beschreiben. Eine Rotation des zugrundeliegenden Gradnetzes (Punkt 2) muss nicht durchgeführt werden, da die Definitionspunkte der Grafikobjekte bereits in geografischen Koordinaten angegeben sind. Das Clipping (Punkt 8) kann eventuell entfallen, wenn die Kartenelemente komplett übernommen werden sollen (z.B. gesamte Deutschlandkarte), es muss auf jeden Fall nur einmal durchgeführt werden.
Die Hauptaufgabe der Konfigurationsdatei ist es, die Wetter- und Kartendaten für die Visualisierung soweit vorzubereiten, dass die Grafikobjekte eines gewünschten Ausschnitts in Koordinatenangaben vorliegen, die direkt als Pixel eines Flash-Films verwendet werden können. Das XSLT-Stylesheet bestimmt nun noch:
<map type="city"> <part id="city1" type="point" label="Hamburg" category="1"> <point x="9.9748" y="53.5358"/> </part> </map>
<?xml-stylesheet type="text/xml"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- movie and shape layout --> <xsl:variable name="moviewidth">800</xsl:variable> <xsl:variable name="movieheight">600</xsl:variable> <xsl:variable name="movierate">1</xsl:variable> <xsl:variable name="moviebgcolor">FFB0B0B0</xsl:variable> <xsl:variable name="labeltextcolor">FF000000</xsl:variable> <xsl:variable name="labeltextsize">8</xsl:variable> <!-- rule for document root: start and end swfml --> <xsl:template match="map"> <SWF rate="{$movierate}" w="{$moviewidth}" color="{$moviebgcolor}" h="{$movieheight}"> <xsl:apply-templates select="part" mode="defineshapes"/> <xsl:apply-templates select="part" mode="placeshapes"/> </SWF> </xsl:template> <!-- define all cities --> <xsl:template match="map[@type='city']/part" mode="defineshapes"> <ellipse ID="{@id}" lc="FF000000" lw="1" color="FFFF0000" x="}\{floor(point/@x) - 2\}\textcolor{blue}{" w="4"} y="}\{floor(point/@y) - 2\}\textcolor{blue}{" h="4"/>} <text ID="{@id}label" color="{$labeltextcolor}" size="{$labeltextsize - @category}" x="{floor(point/@x) - $labeltextsize}" y="{floor(point/@y - $labeltextsize)}"> <xsl:value-of select="@label"/> </text> </xsl:template> <!-- rule for all points --> <xsl:template match="point"> <point x="{floor(@x)}" y="{floor(@y)}"/> </xsl:template> <!-- place city shapes --> <xsl:template match="map[@type='city']/part" mode="placeshapes"> <PlaceObject ID="{@id}" depth="3"/> <PlaceObject ID="{@id}label" depth="4"/> </xsl:template> ... </xsl:stylesheet>Quellcode 6.5: Ausschnitt aus
map2swfml.xslt
Der Aufbau der XSLT-Datei weather2swfml.xslt, die die Anweisungen für die Übersetzung einer WeatherML-Quelle nach SWFML enthält, ist nur unwesentlich komplizierter. Für jeden Parametertyp (Temperatur, Niederschlag, ...) ist den Isoflächen bzw. den daraus resultierenden Polygonen die dem Füllindex entsprechende Füllfarbe zuzuordnen. Die Anzahl der möglichen Füllfarben wird durch die Anzahl der Isowerte bestimmt, die während der Vektorisierung verwendet wurden.
Eine Applikation, die alle bisher entwickelten Komponenten verbindet und die Erzeugung einer SWFML-Datei aus einer MapML-, einer WeatherML- oder einer GRIB-Datei ermöglicht, setzt sich aus den folgenden Anweisungen zusammen:
java Grib2ml gribfile recordnumber configfile outputfile
java Grib2ml gribfile recordnumber configfile xsltfile outputfile
java Grib2ml dwd_tmp_199912261500.grb 1 grib_dwd_tmp.configml \
weather2swfml.xslt tmp15.swfml
// get GRIB record GribFile grib = new GribFile( args[0] ); int recordnumber = Integer.parseInt( args[1] ); GribRecord gribrec = grib.getRecord( recordnumber ); // create WeatherDocument from GRIB record WeatherDocument weather = new WeatherDocument( gribrec ); // read configuration and transform graphic objects ConfigDocument config = new ConfigDocument( args[2] ); weather.modify( config ); // use XSLT stylesheet during output of graphic objects FileInputStream xslt = new FileInputStream( args[3] ); OutputStream out = new FileOutputStream( args[4] ); weather.write( xslt, out );Quellcode 6.6: Ausschnitt aus
Grib2ml.java
Reading GRIB file information... GRIB record: IS section: Grib Edition 1 length: 117980 bytes PDS section: parameter: Temperature level: 2 m above ground time: 26.12.1999 16:0 dec.scale: 0 GDS exists BMS exists GDS section: Rotated LatLon Grid (325x325) lon: -12.5 to 7.75 (dx 0.062) lat: 3.25 to -17.0 (dy -0.063) south pole: lon 10.0 lat -32.5 rot angle: 0.0 BMS section: bitmap length: 105625 BDS section: min/max value: -15.167572 20.332428 ref. value: -15.167572 bin. scale: -2 num bits: 8 Generalizing... grid size before: 325x325 ... combining 3 columns and 3 rows ... new grid size: 109x109 Projecting... rotated lat/lon grid -> geographic coordinates Projecting... geographic coordinates -> Azimuthal Lambert projection reference point: (10.0,50.0) new bounds: x=[-0.21727598, 0.13271724] y=[-0.17233898, 0.18625306] Resizing grid extent... new bounds: x=[-912.5591, 557.4124] y=[-782.2628, 723.8237] Moving grid... new bounds: x=[-532.5591, 937.4124] y=[-396.26285, 1109.8237] Clipping... grid size before clip: 109x109 clip window: min=(-20.0,-20.0) max=(850.0,620.0) ... clipping ... grid size after clip: 66x48 Vectorizing... iso values: -25.0 -20.0 -15.0 -10.0 -5.0 0.0 5.0 10.0 15.0 20.0 25.0 30.0 35.0 40.0 ... searching for iso values and building polygons ... ... cleaning up polygons ... found 20 polygons (10 internal) with 1382 points and 37 labels Clipping... 20 shapes before clip clip window: min=(0.0,0.0) max=(800.0,600.0) ... clipping ... 17 shapes after clip Building weather document... Translating document... Printing document...Quellcode 6.7:
Grib2ml
-Kontrollausgabe
Zwei weitere Applikationen, die SWFML-Dateien erzeugen, sind Weather2ml und Map2ml. Beide Java-Programme lesen WeatherML- bzw. MapML-Dateien ein und geben dasselbe Format wieder aus oder wandeln es nach SWFML um, sofern ein XSLT-Stylesheet - z.B. weather2swfml.xslt bzw. map2swfml.xslt - als Kommandozeilenparameter angegeben wird.
Die Applikation Swfml2Swf erzeugt abschließend aus allen SWFML-Dateien den Flash-Film. Swfml2Swf wurde bereits in Abschnitt 5.7 vorgestellt. Der Quellcode befindet sich mit allen anderen Programmen dieser Beispielanwendung der FlashWeather-Bibliothek auf der beigefügten CD-ROM im Verzeichnis /beispiel.
Um die Lauffähigkeit der oben genannten Progamme zu testen und ihren Ressourcenbedarf zu messen, wurde die Erzeugung von Flash-Wetterfilmen auf folgenden Rechnern und Betriebssystemen durchgeführt:
Der Test umfasst die folgenden vier Programmaufrufe, bei denen die Konfigurationsdateien und XSLT-Stylesheets aus Abschnitt 6.3 zum Einsatz kommen:
java Map2ml country.mapml map_deu.configml \ map2swfml.xslt country.swfml
java Grib2ml dwd_tmp_15.grib 1 grib_dwd_tmp.configml \ weather2swfml.xslt tmp15.swfml
for I in 00 01 02 03 04 05 06 07 08 09 10 11 \ 12 13 14 15 16 17 18 19 20 21 22 23 do java Grib2ml dwd_tmp_"$I".grib 1 grib_dwd_tmp.configml \ weather2swfml.xslt tmp"$I".swfml done
java Swfml2Swf country.swfml river.swfml lake.swfml city.swfml \ tmp*.swfml tmp.swf
Auf jedem der vier Testrechner wurden alle vier Programmaufrufe durchgeführt. Die Laufzeit wurde mit dem time-Kommando gemessen und der maximale Arbeitsspeicherbedarf anschliessend in einem unabhängigen Test mit Hilfe von top oder dem NT-Taskmanager ermittelt. Die Testergebnisse können daher Ungenauigkeiten enthalten, geben aber eine recht gute Größenordnung der Laufzeit und des verwendeten Hauptspeichers wieder.
Die Ergebnisse der Testläufe enthält Tabelle 6.1.
|
Tabelle 6.1: Messergebnisse der Testläufe
Eine Optimierung der Laufzeit ist jederzeit durch die Flagge -Xms<Speichergröße> des Java-Interpreters möglich, die der Applikation schon zu Beginn der Ausführung die angegebene Menge Arbeitspeicher reserviert. Z.B. verkürzt der Aufruf
java -Xms20MB Grib2ml dwd_tmp_15.grib 1 grib_dwd_tmp.configml \ weather2swfml.xslt tmp15.swfml
Vom Deutschen Wetterdienst stehen Prognosedaten für den 26.12.1999 zur Verfügung, die die Parameter Temperatur, Niederschlag, Bewölkungsgrad und Luftdruck umfassen. Das rote Rechteck in Abbildung 6.2 markiert das Gebiet, das die Daten abdecken. Die daraus erzeugten Flash-Filme zeigen die Abbildungen 6.3 bis 6.6.
Weitere GRIB-Dateien wurden von einem Internetrechner des National Weather Service (NWS) der USA heruntergeladen [NWS2000]. Sie enthalten die Wetterprognose für die gesamte Erde, die Auflösung des Rasters ist jedoch viel geringer als bei den Daten des DWD. Außerdem liegt zwischen den Prognosen ein zeitlicher Abstand von drei Stunden. Die Abbildungen 6.7 und 6.8 zeigen Momentaufnahmen aus möglichen Wetterfilmen.