|
4.5 ObjekttransformationenAlle Transformationen werden zunächst mit dem Attribut transform eingeleitet. Als Wert wird anschließend die gewünschte Transformation angegeben. Zur Verfügung stehen dabei translate (Verschiebung), scale (Skalierung), rotate (Rotation), skewX, skewY (Scherung in x- oder in y-Richtung) und matrix (echte Matrixtransformation). Transformationen können auch verkettet werden, um z. B. gleichzeitig eine Translation und eine Skalierung durchzuführen. Bedenken Sie dabei, dass es sich um affine geometrische Transformationen handelt, im Fall einer Hintereinanderschaltung werden sie von hinten beginnend abgearbeitet. Daraus ergibt sich in weiterer Folge, dass die Reihung innerhalb der Kette Auswirkungen auf die Darstellung hat. 4.5.1 Verschieben von Objekten mit translateFür eine Verschiebung wird dem Attribut transform der Wert translate(x y) zugewiesen, wobei x und y für die jeweilige Verschiebung entlang der Achsen steht. Hier werden die gewünschten Einheiten angegeben. Möchte man ein Objekt mit seinem Mittelpunkt beispielsweise auf den Ursprung 0,0 verschieben, muss man zunächst wissen, wo es positioniert wurde und wie seine Maße sind. Bei einem Kreis ist das relativ einfach: Der Mittelpunkt wird bereits bei der Positionierung definiert, die Werte müssen dann bei dem Wert von translate angegeben werden: 41240.svg <circle id="myCircle" cx="400" cy="200" r="70" fill="orange" stroke="lightred" stroke-width="3"/> 4.5.2 Spiegeln und Skalieren mit scaleFaktisch werden mit dem Wert scale(x y) die Koordinaten mit den angegebenen Werten multipliziert, wobei sich der erste Wert auf die x-Werte, der zweite Wert auf die y-Werte bezieht. <g transform="scale(1 -1)">
In unserem Beispiel wurden also die x-Werte mit 1 multipliziert, was zu keiner Veränderung führt, und die y-Werte mit -1, was bewirkt, dass allen y-Werten ein „-“ vorgestellt wird. Man könnte annehmen, dass man den Wert 1 auch einfach weglassen könnte – schließlich ändert er ja nichts an der Darstellung. Sie können ja einfach einmal ausprobieren, was passiert, wenn Sie die 1 weglassen. Sie werden feststellen, dass dann beide Richtungen x und y mit -1 multipliziert werden, wodurch unsere Graphik nicht nur in der Vertikalen, sondern auch in der Horizontalen umgekehrt würde. Wenn Sie also nur einen Wert angeben, gilt dieser sowohl für die x-Richtung als auch für die y-Richtung. Abb. 4.8: Die Küstenlinie Korsikas wurde mit dem Skalierungsfaktor 2 vergrößert. Dadurch verschiebt sie sich allerdings auch (vgl. Beispiel 41238.svg). Statt mit 1 oder -1 zu multiplizieren, kann man natürlich auch jede andere beliebige Zahl nehmen. Dies bewirkt dann eine Skalierung unseres Objekts. Dabei muss allerdings berücksichtigt werden, dass nicht nur die dargestellten Werte des Objekts (bei einem Rechteck also z. B. width und height) diese Transformation erfahren, sondern das gesamte Koordinatensystem, in dem das Objekt positioniert wurde. Um ein Objekt an der Position zu vergrößern, an der es sich befindet, muss man deshalb einen Trick anwenden. Bei den Grundformen wie einem Rechteck oder dem Kreis, bei dem Höhe, Breite beziehungsweise der Radius und der Mittelpunkt bekannt sind, kann man dieses Problem durch eine einfachen Rechnung lösen. Bei komplexeren Formen wie unserer Küstenlinie ist das nicht ganz so einfach. Sehen wir uns deshalb diese Problematik einmal an einem einfacheren Beispiel an: 41239.svg <rect id="myRect" x="200" y="200" width="100" height="70" fill="orange" stroke="orange" stroke-width="3"/> Die Skalierung am Mittelpunkt des Rechtecks wird dadurch ermöglicht, dass nach der Skalierung das Rechteck mit seinem Mittelpunkt wieder zurück an seinen alten Platz verschoben wird. Um die Ausmaße komplizierterer Elemente und Gruppen zu erfahren, gibt es die Methode .getBBox(), siehe Kapitel 11.3, Seite 198. Abb. 4.9: Ein Rechteck wird an seinem Mittelpunkt skaliert (41239.svg). transform="translate(-250,-235) scale(2)" bewirkt also, dass zunächst um den Faktor 2 skaliert und dann um 250 Einheiten nach links und 235 Einheiten nach oben verschoben wird. Dadurch sitzt das Rechteck wieder mit dem Mittelpunkt an seinem alten Platz. Dreht man diese beiden Teiltransformationen um, ergibt sich ein anderes Ergebnis (vgl. Abb. 4.10). 41241.svg <rect id="myRect" x="200" y="200" width="100" height="70" fill="orange" stroke="orange" stroke-width="3"/> Abb. 4.10: Das Rechteck wurde erst verschoben und dann skaliert (41241.svg). 4.5.3 Rotieren von Objekten mit rotateMit rotate können Objekte gedreht werden. Dafür wird dem Attribut transform der Wert rotate(Winkel) zugewiesen, wobei der Winkel in Grad (ohne Einheit) angegeben wird und im Uhrzeigersinn gedreht wird. Soll gegen den Uhrzeigersinn gedreht werden, kann auch ein negativer Wert angegeben werden. Allerdings ist dabei zu beachten, dass – egal in welche Richtung – das Objekt am Koordinatenursprung gedreht wird. Ist das nicht gewünscht, kann ein Koordinatenpaar als Ursprung der Drehung angegeben werden. Dafür wird nach dem Winkel, abgetrennt durch jeweils ein Leerzeichen, der gewünschte x und y Wert angegeben, z. B. der Mittelpunkt eines Rechtecks. Abb. 4.11: Das Rechteck wurde einmal um den Ursprung und einmal um seinen Mittelpunkt gedreht (41242.svg). 41242.svg <rect id="myRect" x="100" y="100" width="100" height="70" fill="orange" stroke="orange" stroke-width="3"/> 4.5.4 Scherung mit skewX oder skewYEher selten Verwendung findet die Scherung. Bei der Scherung wird jeweils nur eine Koordinatenachse am Ursprung gedreht und dadurch das Objekt entsprechend verzerrt. 41243.svg <path id="myCoastline" d="M349 458l-... 0 0z"/> Mit skewX(Winkel) wird die y-Achse entgegendem Uhrzeigersinn um den angegebenen Wert (in Grad) gedreht, mit skewY(Winkel) die x-Achse mit dem Uhrzeigersinn. Abb. 4.12: Der Leuchtturm wirft mithilfe von skewX einen Schatten (91453.svg). 4.5.5 Es geht auch komplizierter: die MatrixtransformationGenerell können Sie jede beliebige affine Transformation mit einer 3×3 Matrix umsetzen. Leider ist diese Art der Transformation nicht ganz so leicht nachzuvollziehen. Angewendet wird diese, indem dem Attribut transform der Wert matrix(a b c d e f) zugewiesen wird, wobei a–f für die ersten sechs Elemente der Matrix stehen. Die letzten drei bleiben im 2D-Raum mit 0, 0 und 1 immer gleich. Abb. 4.13: Transformationsmatrix zur Berechnung eines neues Koordinatensystems Dabei betragen die Elemente bei der sogenannten Einheitsmatrix (die Matrix, die auf das initiale Koordinatensystem angewandt wird) an den Positionen a und d jeweils 1, während die anderen vier Elemente den Wert 0 bekommen. Mit der Transformationsmatrix können allerdings auch alle Transformationen durchgeführt werden, die wir bisher schon kennen gelernt haben. Bei der Skalierung werden die Faktoren für x an die Position a und y an die Position d gesetzt, die restlichen vier Elemente behalten den Wert 0. Das heißt, wenn in x Richtung mit dem Faktor 2 und in y Richtung mit dem Faktor 4 skaliert werden sollte, müsste der Wert für das transform-Attribut matrix(2 0 0 4 0 0) lauten. Für eine Verschiebung werden den letzten beiden variablen Elementen die jeweiligen Werte für die Verschiebung in x- und in y-Richtung zugewiesen, während die anderen ihren Wert der Einheitsmatrix behalten. Damit ergibt sich für eine Verschiebung um 400 Einheiten in x-Richtung und 300 Einheiten in y-Richtung als Wert für das transform-Attribut matrix(1 0 0 1 400 300). Die Rotation muss in der Matrix mittels den Sinus- beziehungsweise Cosinus-werten erfolgen (matrix(cos(a) sin(a) sin(a) cos(a) 0 0). Leider kann hier nicht einfach sin(a) angegeben werden, sondern der Wert muss vorher ausgerechnet werden, also würde für eine Drehung um 45° die Matrix die Werte 0.707 0.707 0.707 0.707 0 0 erhalten. Ein Element z. B. an seinem Mittelpunkt zu rotieren, ist nicht so leicht wie bei der Rotation mittels einer Grundtransformation. Denn bei den Matrizen werden die einzelnen Transformationen in einem Satz ausgeführt (es handelt sich konkret um Matrixmultiplikationen), so dass nicht einfach der Ursprung der Rotation angegeben werden kann. Für die Scherung gibt es wieder nur jeweils einen Wert: den Tangens des Winkels, wobei dieser für eine Scherung in x-Richtung an der Position c steht, bei der Scherung in y-Richtung an der Position b. Für alle Arten der Transformation gilt, dass die Reihenfolge entscheidend ist. Im Gegensatz zu üblichen Zeichenprogrammen ist es nicht gleichgültig, ob ein Objekt erst gedreht und dann verschoben wird oder andersherum. Denn es wird immer auch das gesamte Koordinatensystem mit transformiert, so dass eine Verschiebung auf das gedrehte Koordinatensystem angewandt wird, wenn zuerst die Verschiebung und dann die Rotation angegeben wird. 6.3Komplexe LiniensignaturenIn Kapitel 5.11 haben wir bereits gezeigt, dass es möglich ist, Text am Pfad auszurichten. Die dabei verwendete Technik erlaubt es uns jedoch, komplexe Pfadsignaturen zu gestalten, die mit einem normalen Pfad nicht möglich wären. Unter komplexen Liniensignaturen verstehen wir Signaturen für linienhafte Elemente, die nicht nur aus einer einfachen Linie wie bei den bisher dargestellten Flüssen oder einer doppelt dargestellten Linie, wie es bei den Straßen der Fall war, bestehen, sondern die zusätzliche Symbole oder Asymmetrien benötigen wie etwa bei der Darstellung von Zäunen oder Kanälen. Das <path>-Element hat in SVG 1.1 leider keine zusätzlichen Attribute, in denen man solche Darstellungen einbinden könnte, deshalb muss man sich mit einem kleinen Trick helfen: der Anordnung von Text am Pfad. Um beispielsweise einen Zaun darzustellen, könnte man eine Reihe von „v“ an einem Pfad entlangreihen. 39710.svg <path id="myPlot" fill="none" stroke="black" stroke-width="1" d="M20 20 v 300 h 400 v -300 z"/> Dafür wird zunächst ein Pfad definiert, der in diesem Fall unsere Grundstücksgrenze darstellt. Als Kindelement des <text>-Elements wird nun ein <textPath>-Element erstellt, welches als Referenz mit dem Attribut xlink:href den zuvor erstellten Pfad enthält. Alternativ hätte man den Pfad auch direkt im <textPath>-Element mit dem d-Attribut definieren können; dies macht das Dokument aber meist nur unnötig unübersichtlich. Wichtig bei der Definition des Pfads ist die Richtung. In diesem Beispiel ist der Pfad von links oben beginnend nach links unten und weiter gegen den Uhrzeigersinn gezeichnet worden, damit später die „Zaunecken“ in der Fläche liegen. Wäre der Pfad anders herum gezeichnet worden, hätte man die Buchstaben aber einfach nur umzudrehen brauchen. Wenn man das beeinflussen kann, bietet es sich aber an, die Pfade gleich richtig herum zu definieren. Besonders kompliziert war diese Liniensignatur noch nicht. Anspruchsvoller wäre die Zaunsignatur, wie sie zum Teil für topographische Karten verwendet wird, welche nur aus senkrecht zur Grenzlinie mittig stehenden Strichen besteht. Wenn wir uns diese ebenfalls als Umsetzung mithilfe von einfachen Zeichen vorstellen, könnten die Striche jeweils aus „|“ (Pipe-Zeichen) bestehen, die durch das Attribut baseline-shift nach unten gesetzt wurden: 39711.svg <text id="myFence" baseline-shift="-3"> Abb. 6.6: Darstellung einer Zaunsignatur für großmaßstäbige topographische Karten, z. B. 1:5000 (links) und 1:25000 (rechts). Eine Reihe von „v“ beziehungsweise „|“ wurde am Pfad entlanggeführt und im zweiten Fall um etwas mehr als die Hälfte nach unten, beziehungsweise außen, verschoben (vergleiche Beispiele 39710.svg und 39711.svg). Eine kleine Herausforderung wäre schon die Signatur für einen Kanal, indem kleine Quadrate entlang des Kanalverlaufs mitgeführt werden. Dafür kann man nun keinen regulären Buchstaben mehr nehmen, aber wie Sie in Kapitel 5.4 erfahren haben, können eigene Zeichen, Graphiken oder Symbole als Schriftzeichen definiert werden. Wir definieren also ein Quadrat so, dass es mit einem bestimmten Buchstaben in einer bestimmten Schriftart aufgerufen wird, also beispielsweise ein q in der Schriftart myFont. Der Buchstabe wird innerhalb des <defs>-Bereichs mithilfe der Elemente <font> (das äußerste Element, mit dem die Schriftart eingeleitet wird), <font-face> (hier wird die Schriftart genauer defniert) und <glyph> (jetzt kommt der eigentliche Buchstabe) definiert (vergleiche Kapitel 5.4). In diesem Beispiel wird der Buchstabe q durch ein blaues Rechteck definiert und anschließend am Pfad ausgerichtet. 39712.svg <defs> Abb. 6.7: Kanalsignatur mithilfe eines selbst definierten Zeichens, welches dann am Pfad ausgerichtet wurde (vergleiche Beispiel 39720.svg). Oben z. B. ein schiffbarer Kanal mit einer doppelten Linienführung und durch baseline-shift nach außen gesetzten Quadraten. Natürlich können so auch zusammengesetzte Liniensignaturen definiert werden, wie der schiffbare Kanal (vergleiche Beispiel 39714.svg und Abbildung 6.7). Zu beachten ist dann, dass der „Text“ entweder mithilfe von baseline-shift höher gesetzt werden muss, damit er an der äußeren Kante der Linie zu sehen ist oder entsprechend das Symbol verändert werden muss. Sinnvoller ist wohl die Verwendung der Eigenschaft baseline-shift, da somit das gleiche Symbol z. B. für die schiffbaren und die unschiffbaren Kanäle verwendet werden kann. Ihrer Phantasie sind somit keine Grenzen gesetzt! Als Beispiel für eine unregelmäßige Liniensignatur sei noch folgendes Beispiel angeführt: 39715.svg <font> Hier wurde mit zwei Rechtecken, von denen das eine quadratisch und das andere länglich ist, eine dünne und eine dickere Linie erzeugt. Zu beachten ist dabei, dass die Einheit, die durch dieses „Zeichen“ dargestellt werden soll, etwas kleiner als die Breite der Rechtecke sein sollte, damit keine „Blitzer“ am Rand entstehen. Darüber hinaus ist zu berücksichtigen, dass die Rechtecke selbst relativ schmal definiert werden sollten, damit sich keine Ecken bei der Anordnung am Pfad abzeichnen, wie es nachfolgende Abbildung 6.8 (rechts, vergleiche 39716.svg), zugegebenermaßen etwas übertrieben zeigt. Abb. 6.8: Unregelmäßige Liniensignaturen. Links wurden die Rechtecke schmal genug definiert, damit sie nicht zu sehen sind (vergleiche Beispiel 39715.svg). Hingegen wurden sie rechts zu breit (width="100") definiert: Die einzelnen Rechtecke sind deutlich zu sehen (vergleiche Beispiel 39716.svg). SVG eignet sich auch für die Darstellung von Wetterkarten, z. B. mit Kalt- und Warmfronten, wie nachfolgendes Beispiel (52361.svg) zeigt. Hier wird noch einmal deutlich, worauf bei der Definition der Glyphen besonders geachtet werden sollte:
Abb. 6.9: Selbst definierte Glyphen eignen sich auch für Wetterkarten. Hierbei ist allerdings zu beachten, dass die untere Mitte des Glyphen als Ankerpunkt an der Linie verwendet wird (vergleiche Beispiel 52361.svg und 52362.svg). 12.6Level of Detail (LOD) und Daten nachladenDa wir nun ziemlich genau mitverfolgen können, in welchem Maßstab die angezeigte Karte gerade am Bildschirm des Anwenders ist, wäre es wohl interessant, geometrische Daten nachzuladen, beziehungsweise bei Bedarf Daten auszublenden. Grundsätzlich setzt dies auf der Serverseite eine entsprechende Logik voraus. Da wir in diesem Buch aber nicht auf Servereinsatz eingehen, werden wir uns mit vorgefertigten Elementen begnügen. Wenn man Elemente nachlädt, handelt es sich um Dateifragmente, welche in sauberem XML vorliegen müssen. In diesem Sinne ist es egal, ob die Daten von einem serverseitigem Script bereitgestellt werden oder ob wir statische Dateien aufrufen. Die Zoomstufe kann aus Berechnungen von viewBox und Viewport, über .currentScale oder .getScreenCTM().a/.d erfasst werden. Da .getScreenCTM() ein Verhältnis zwischen Kartenkoordinaten und Bildschirmpixel herstellt, wäre dies der sauberste Zugang. Wir haben aber gesehen, dass diese Methode noch nicht alle Viewer einwandfrei unterstützen, und werden daher im Folgenden .currentScale einsetzen. Die Funktionsweise wäre dieselbe, mit .currentScale haben wir den Vorteil, mit einfacheren Zoomfaktoren rechnen zu können. Zuvor wieder die logischen Überlegungen: Wir sind im Zoomlevel X und gelangen durch Anwendereinwirkung in den höheren Level Z, der nun über dem Schwellwert Y liegt. Was muss passieren?
Zu erfassen, ob etwas da ist oder nicht, ist noch recht einfach. Bei der Entscheidung „löschen“ oder „unsichtbar machen“ kommt es auf die Datenmenge an, allerdings sind große Mengen immer problematisch. Löscht man nicht, kann die Datei am Rechner des Anwenders anschwellen (was auch Probleme bereiten kann); löscht man und fügt immer wieder hinzu, kommt es zu Transferzeiten. Streng genommen reicht es natürlich nicht aus, die Zoomstufe zu überwachen. Wenn der Anwender in das nordwestliche Eck einer Karte hineinzoomt, sollten nur dort Daten nachgeladen werden. Wir kennen zwar diese Werte aus der Mitführung sämtlicher Kartenparameter, aber dies lassen wir hier aus, da dann eine räumliche Datenbank auf der Serverseite von Nöten wäre. Obwohl es schon recht aufwändig ist, nehmen wir das Beispiel 99111.svg als Basis für unsere LOD-Versuche. Immerhin gibt es dort ein praktisches Interface, mit dem man schnell den Zoomlevel verändern kann. Für LOD ohne Nachladen sei die URL763 als einfaches Beispiel empfohlen. Die neue Datei heißt 99900.svg und wurde entkernt, sie enthält nur mehr den Kartenrahmen (als myPassiveElements), die Schummerung, das Meer und daraus resultierend die Küste. In der Übersichtskarte referenzieren wir nicht mehr die Kantone (denn sie sind ja nicht mehr da), sondern das Meer mySea. Abgesehen von diesen graphischen Änderungen bleiben all die Zoom- und Pan-Funktionen vorerst unangetastet. Um hier zusätzliche Daten anzuzeigen, braucht es eine Gruppe im DOM, in die die neuen Geometrien eingehängt werden können. Wir haben dies schon gesehen, als es darum ging, Signaturen zu generieren. Eine leere Gruppe (<g id="myTempArea"/>) kann hier dienen. Diese ist in der Datei 99901.svg bereits zu finden. Und natürlich ist eine Funktion nötig, die diese Daten einspielt. Hier müssen wir uns wieder um die unterschiedlichen Viewer kümmern. Batik Squiggle 1.6 und der Adobe SVG Viewer 3.x setzen die ältere und nicht standardisierte Methode getURL() ein, Mozilla Firefox 1.5 jedoch XMLHttpRequest(). Diese Funktionen machen hier beide dasselbe, nämlich asynchron Daten holen. In Scripts werden normalerweise alle Befehle unmittelbar (also synchron) abgearbeitet. Wenn wir nun aber zusätzliche Daten nachladen, so handelt es sich um den Aufruf einer meist entfernten Ressource. Die Zeit, die diese benötigt, um anzukommen, ist nicht vorhersehbar und vergleichbar mit dem ursprünglichen Laden der Grunddatei. Deswegen arbeiten die Methoden mit sogenannten Callback-Funktionen, die ausgeführt werden, wenn die Daten da sind. getURL() und XMLHttpRequest() haben eine Vielzahl von Eigenschaften, die man zusätzlich abfragen kann. Wir interessieren uns aber nur für das Holen der Daten und für das Einfügen, sobald sie verfügbar sind. Deshalb sind die Funktionen kompakt gehalten. Zum besseren Verständnis werden wir sie getrennt betrachten, später aber in einer Funktion zusammenfassen, damit man sie schnell und einfach einsetzen kann. Beide Methoden brauchen zwei Übergabewerte: das zu holende Dateifragment (myURL) und eine Gruppe im DOM, innerhalb derer das Dateifragment anschließend eingehängt werden soll (myDestination). Wir werden diese beiden Methoden anhand einer eigenen Datei durchspielen: 49122.svg. 12.6.1 getURL() und parseXML()Die vordefinierte Funktion getURL() erwartet zwei Übergabewerte: den Pfad zur externen Ressource und eine Callback-Funktion, welche ausgeführt wird, wenn die Daten da sind. Das ist etwas verkürzt ausgedrückt, denn genau genommen kann es vorkommen, dass die Callback-Funktion mehrmals aufgerufen wird, weil getURL() mehrere „Zustände“ hat, die alle übermittelt werden. Daher wird innerhalb der Callback-Funktion myFileLoaded() nur nach einer Bedingung weitergearbeitet. Die Callback-Funktion hat ihre Eigenheiten. Zum Ersten wird sie in Zeile 2 (unten) ohne Übergabewert aufgerufen. Das liegt daran, dass myFileLoaded() als Objekt betrachtet wird, dessen Eigenschaften implizit im Übergabewert (später myData) definiert sind. Zum Zweiten befindet sich die Funktion myFileLoaded() verschachtelt in der Funktion myGetURL(). Das ist Script-technisch zulässig und folgendermaßen begründet: Die Variable myDestination benötigen wir in Zeile 11 für die DOM-Manipulation. Aber man kann sie myFileLoaded() nicht mit auf den Weg geben, da in Zeile 2 keine Übergabevariablen festgelegt werden können. Durch das Verschachteln bleibt die Variable myDestination auch in der inneren Funktion verfügbar. Die übergebenen Daten und „Zustände“ des getURL()-Aufrufs sind nun in der Objektvariablen myData zu finden. Die Eigenschaft .success (Zeile 5) ist belegt, wenn die Transaktion stattfinden konnte. Unter .content sind die Daten zu finden (Zeile 6). Schlägt die Transaktion fehl, so wird dies in Zeile 8 abgefangen. Hier könnte man gegebenenfalls Fehlermeldungen spezifizieren. Geht alles gut, sind noch zwei Schritte nötig: Die übergebenen Daten müssen mit parseXML() in einen interpretierbaren Knoten umgewandelt werden (Zeile 10). Bis hierher handelte es sich lediglich um eine Zeichenkette (das Ganze muss an einem Referenzknoten ausgeführt werden, hier document). Die letzte Zeile ist bekannte Materie: einfügen. 49122.svg function myGetURL(myURL,myDestination,myEncoding){ Die Variable myEncoding ist nur für Batik Squiggle 1.6 nötig, da dieser Dateien im Default-Java-Encoding weiterreicht und das ist unter Windows ANSI (ASCII). Normalerweise reicht es aus, wenn alle betroffenen Dateien in einem einheitlichen Encoding sind, und zwar UTF-8. Siehe auch Kapitel 9.1.2 auf Seite 136, dort geht es zwar um externe Script-Dateien, das Problem ist aber hier das gleiche. Die Methode parseXML() kann auch in einem anderen Zusammenhang interessant sein. Man kann ihr nämlich auch ganze XML-Strings übergeben und so das langwierige Zusammensetzen von Elementen mittels .createElement und .setAttributeNS umgehen. In Beispiel 78886.svg (siehe auch Listing auf Seite 144) haben wir ein beziehungsweise mehrere Rechtecke mit .createElement gebildet. Mit parseXML() würde dies folgendermaßen aussehen: var myNewRect = parseXML('<rect x="10"y="100" Beachten Sie aber, dass diese Methoden nicht in strikt DOM2-kompatiblen Viewern einsetzbar sind. 12.6.2 XMLHttpRequest()Die Methode XMLHttpRequest() erwartet ebenfalls die Übergabewerte myURL und myDestination wie getURL() oben. Man erzeugt eine Instanz von der Methode, die wir myReq nennen (siehe Zeile 2 unten). Mit .open() wird die Ressource adressiert und mit .send() gestartet (.send() wird auch für die andere Richtung verwendet, daher ist der Übergabewert hier leer). .onreadystatechange ist mit .success von getURL() vergleichbar, außer dass hier verschiedene Stadien durchlaufen werden (konkret das Anfragen der Ressource, die Übertragung etc). Die Daten sind erst verfügbar, wenn .readyState den Wert 4 hat (Zeile 7). 49122.svg function myXMLHttpRequest(myURL,myDestination){ An .onreadystatechange wird auch die Callback-Funktion definiert (Zeilen 6–11). Nun sind unsere Daten in der Eigenschaft .reponseXML verpackt. Hier handelt es sich bereits um einen einsetzbaren Dokumentknoten (auch wenn es nur ein Dateifragment ist). Wir nehmen den Dokumentknoten (.documentElement), aber man könnte hier auch DOM-basiert einzelne Elemente selektieren. Das Ganze wird dann ins DOM des Hauptdokuments eingefügt (Zeile 8). 12.6.3 Gemeinsamkeiten beim NachladenEs gibt Prämissen, welche die nachgeladenen XML-Fragmente erfüllen müssen:
Nun wäre es natürlich interessant, XMLHttpRequest() und getURL() in einer Funktion zu verbinden, so dass der Aufruf unabhängig vom Viewer erfolgen kann. Das ist ebenfalls in der Datei 49122.svg umgesetzt, die Funktion myAddXML() übernimmt beide Methoden. Hier kann man übrigens auch vergleichen, wie sich das Encoding auf die Darstellung von Text auswirkt. Kehren wir also zur Datei 99901.svg zurück. Dort werden beim Laden auch schon versuchsweise zwei Ebenen geladen, ein paar Straßen und bis jetzt nicht genutzte Gebietseinheiten (Arrondissements): 99901.svg function myHandleLoad(){ Aber dies ist nicht besonders intelligent, denn die Ebenen sind nicht unbedingt richtig gereiht. Zwar könnte man die Aufrufe so reihen, dass die Ebenen am Ende richtig geordnet sind, aber das ist nur Theorie, denn diese Aufrufe erfolgen alle asynchron. Es ist nicht vorhersehbar, welches Fragment zuerst vollständig geladen wird. Die Funktion myAddXML() macht nichts anderes, als die eintreffenden Teile nacheinander in die Ebene myTempArea einzufügen. Am einfachsten ist es, leere Fächer für alle möglichen Ebenen vorzusehen. Wir haben von oben nach unten:
Es müssen also verschachtelte Gruppen erstellt werden. Für Ebenen, die gleichzeitig vorhanden sein können, braucht man dementsprechend viele Unterfächer, für die anderen jeweils eines. Um die Scripts einfacher zu halten, gibt es jedoch für jede mögliche Datei ein eigenes Fach. 99902.svg <g id="myTempArea"> Bevor wir an der Funktion, die das Ein- und Ausschalten der Ebene vornimmt, arbeiten können, legen wir noch eine Funktion zum Löschen von Gruppen an. Wie wir gesehen haben, muss man dabei Vorkehrungen treffen. Unsere Funktion myClearElem() (in der Datei 99902.svg) basiert auf jener aus dem Beispiel 98801.svg (Seite 196). Wir spielen das Prozedere vorerst für eine Ebene beziehungsweise eine XML-Datei durch. Abhängig vom Zoomlevel (Zeile 3 unten) muss eine Ebene geladen werden, aber nur, wenn dort noch kein Datensatz vorhanden ist. Um dies zu überprüfen, erfragen wir die Existenz eines Kindelements der Gruppen ab, in welche die Datei geladen werden soll (Zeile 4). Anderenfalls wird das XML-Fragment geladen (Zeile 5). Beim Löschen gehen wir weniger zimperlich vor, außerhalb des geltenden Zoombereichs wird einfach entfernt (Zeile 8). Aufgerufen wird myHandleLayers() in der Funktion myHandleZoomPan(), die generell Zoom und Pan kontrolliert. Der Aufruf erfolgt aber nur, wenn sich der Zoomfaktor tatsächlich ändert. 99902.svg function myHandleLayers(){ Dieser eine Layer ist nach dem ursprünglichen Laden und bis einschließlich Zoomstufe 4 eingeblendet und wird dann wieder gelöscht. Umgesetzt auf alle Ebenen wird es so etwas umständlich und alles andere als einfach zu warten. Besser wäre es, eine Tabelle zu haben, in der alle Datensätze gelistet sind, mit URL, Zielgruppen-ID, unterer und oberer Zoomgrenze. Wir können hier auch gleich mitprotokollieren, ob eine Ebene geladen ist oder nicht. Umgesetzt ist dies alles in der Datei 99904.svg. Zuerst brauchen wir den Datensatz, der alle Angaben umfasst: 99904.svg var myLayerArr = [ Die Funktion zum Löschen von Ebenen bleibt, wie sie ist, auch die Funktion für die Sichtbarkeitskontrolle sieht jener in der Datei 99902.svg sehr ähnlich. Es wird das Prozedere nun in einer Schleife durchlaufen (Zeile 3–17 unten). In den Zeilen 4 bis 10 wird wieder der Fall des „Einschaltens“ behandelt, in Zeile 12 wird anderenfalls „ausgeschaltet“. Wir verwenden die Lade- beziehungsweise Sichtbarkeitskontrolle mit der Variablen myLayerArr[i][4] nur zur Anzeige im Infoblock (Zeilen 14–16). 99904.svg function myHandleLayers(){ In der Datei 99903.svg ist eine ähnliche Lösung umgesetzt, jedoch wird keine Ebene mehr gelöscht. Sobald eine Ebene geladen ist, wird sie nur mehr durch Setzen des Attributs display ein- oder ausgeschaltet. Die Level-of-Detail-Technik ist somit auch in SVG schnell umsetzbar. Wir haben hier natürlich nur den einfachsten Weg gewählt, nämlich Datensätze in vordefinierte Ebenen geladen. Komplizierter wird es, wenn man unterschiedliche Datensätze in gleiche Gruppen lädt. Denn dann muss man strengere DOM-Kontrollen anwenden. Eine Sache, die hier völlig ausgeklammert wurde, ist die Anzeige von thematischen Inhalten. Auch diese müssten dem jeweiligen vorliegenden Datensatz angepasst werden. Rein theoretisch würden die kantonsbasierten Daten ausreichen, wenn hier noch die Zuordnung zu den Arrondissements und Départements vermerkt wäre. Die Daten sind absolut, also leicht auf übergeordnete Gebietseinheiten umrechenbar. Das Nachladen mittels XMLHttpRequest() oder getURL() hat neben der Logik und den anzufordernden Datenmengen auch Tücken. In unserem Fall laden und löschen wir bei Änderung des Zoomlevels. Wir haben aber im Verlauf dieses Kapitels gelernt, dass onzoom ein unbequemer Event-Listener ist, der unter Umständen Events mehrfach auslöst. Ohne entsprechende Vorkehrungen kann es also leicht passieren, dass Ebenen doppelt in das Hauptdokument geladen werden. Der Grund ist wieder das asynchrone Vorgehen von XMLHttpRequest() oder getURL(). In Zeile 6 des Listings der Datei 99904.svg (oben) überprüfen wir zwar, ob schon ein Element vorhanden ist, aber das SVGZoom-Event wird sicher schneller ausgelöst als das Nachladen der Dateifragmente. Also ist die Chance groß, dass ein zweites Event diese Kontrolle leicht passiert. Werden Ebenen doppelt geladen, tritt meistens der XML-Fehler der doppelt vergebenen IDs auf. Auch optisch ergeben sich Fehler: Halbtransparente Flächen werden opaker und mehrfach platzierte Schrift bekommt aufgrund der mehrfachen Anwendung von Anti-aliasing einen ausgefransten Rand. Wir verhindern diese Probleme mit der Variablen myOldScale in der Funktion myHandleZoomPan(). Hier wird mittels if(myOldScale != myDocElem.currentScale){...} überprüft, dass zoomabhängige Funktionen nur bei tatsächlichen Zoom-Änderungen ausgelöst werden. Ganz ausschließen kann man solche Fehler aber schwer, da man zu sehr von den Eigenwilligkeiten der Viewer und der Reaktionszeit der Rechner abhängt. Das Grundproblem ist, dass Zoom-Events eigentlich auch asynchron sind, aber das Script synchron weiterarbeitet. Daher ist es immer gut, Auffangfunktionen vorzusehen. In der Datei 99904.svg kümmert sich die Funktion myClearElem() um dieses Problem: Zwar ist je Gruppe immer nur ein geladenes Element zu erwarten, die Löschfunktion ist aber gründlich und entfernt alle angetroffenen Inhalte. So kann eine fehlerhaft doppelt geladene Ebene zumindest bei der nächsten Zoom-Änderung mit entsorgt werden. XMLHttpRequest() und getURL() können zwar unterschiedliche Datentypen transferieren, optimiert sind sie aber für XML. Man kann dieses „Tor“ also auch dazu benützen, Daten nicht geometrischer Art nachzuladen, z. B. in Form von Attributen und/oder Elementen in eigenen Namensräumen. Wenn wir neue Gebietseinheiten laden, kann man dafür sorgen, dass mit den Polygonen bereits nötige statistische Werte „mitgeliefert“ werden. Ein Script, welches mit diesen Daten arbeitet, muss diese zuvor dort abholen. Man kann aber auch weiter gehen und XML-Fragmente nachladen, die völlig SVG-fremd sind und nur Daten umfassen. Siehe auch Kapitel 14.1. |