Test Run

Gekrümmte Linien für Bing Maps AJAX

James McCaffrey

Beispielcode herunterladen.

James McCaffreyDiesen Monat stelle ich Ihnen eine JavaScript-Funktion vor, mit der Sie einem Bing Maps AJAX-Kartensteuerelement eine gekrümmte Linie hinzufügen können, und gehe näher auf die zum Testen der Funktion verwendeten Prinzipien ein.

Um zu verstehen, worauf ich abziele, schauen Sie sich am besten das Bild in Abbildung 1 an. Es zeigt ein Bing Maps AJAX-Kartensteuerelement der Region Denver im Westen der USA. Die benutzerdefinierte JavaScript-Funktion, die Thema dieses Artikels ist, wird verwendet, um die blaue gekrümmte Linie von der Stadt Boulder – Ort der University of Colorado (die eine meiner Töchter besucht) – zum Denver International Airport (den ich häufig frequentiere) zu zeichnen. Die benutzerdefinierte Funktion heißt „AddBezierCurve“. Die AddBezierCurve-Routine ist nicht nur eine äußerst hilfreiche Funktion, wenn Sie jemals mit Bing Maps arbeiten. Sie ist auch ein großartiges Präsentationsmittel, um die Prinzipien von API-Tests zu veranschaulichen, und sie enthält einige interessante mathematische Elemente, die Sie möglicherweise in anderen Programmierszenarien sinnvoll finden.

The Custom Function AddBezierCurve Adds a Curved Line to Bing Maps

Abbildung 1 Die benutzerdefinierte Funktion „AddBezierCurve“ fügt Bing Maps eine gekrümmte Linie hinzu.

In den folgenden Abschnitten werde ich Ihnen zunächst kurz erläutern, wie eine Bézier-Kurve erstellt wird, die die von AddBezierCurve verwendete zugrundeliegende mathematische Methode ist. Anschließend führe ich Sie Zeile für Zeile durch die Funktion, sodass Sie Änderungen am Quellcode vornehmen können, um ihn bei Bedarf an Ihre Erfordernisse anzupassen. Zum Schluss beschreibe ich die allgemeinen Prinzipien und spezifischen Techniken, mit denen Sie AddBezierCurve und ähnliche JavaScript-Funktionen testen können. In diesem Artikel wird angenommen, dass Sie über grundlegende Kenntnisse von JavaScript verfügen. Es wird jedoch nicht vorausgesetzt, dass Sie bereits mit der Bing Maps AJAX-Steuerelementbibliothek programmiert haben. Ich bin der Meinung, dass die hier vorgestellten Themen interessant für Sie sind und eine sinnvolle Ergänzung Ihrer Entwicklungs- und Testtools darstellen können.

Bézier-Kurven

Bézier-Kurven können verwendet werden, um gekrümmte Linien zwischen zwei Punkten zu zeichnen. Obwohl Bézier-Kurven viele Variationen kennen, wird die einfachste Form als „quadratische Bézier-Kurve“ bezeichnet. Diese setzt zwei Endpunkte voraus, üblicherweise P0 und P2 genannt, sowie einen dazwischenliegenden Punkt, P1, der die Form der Kurve festlegt. Sehen Sie sich das Beispiel im XY-Diagramm in Abbildung 2 an.

Constructing Bezier Curves

Abbildung 2 Erstellen von Bézier-Kurven

Die Punkte P0, P1 und P2 sind die ungefüllten roten Kreise im Diagramm. Die beiden Endpunkte sind P0 = (1,2) und P2 = (13,8). Der dazwischenliegende Punkt ist P1 = (7,10). Die Bézier-Funktion akzeptiert einen Parameter, üblicherweise „t“ genannt, mit einem Bereich von 0.0 bis 1.0. Jeder Wert von t generiert einen Punkt zwischen P0 und P2, der auf einer Kurve liegt.

In Abbildung 2 habe ich folgende fünf Werte für t verwendet: 0.0, 0.25, 0.50, 0.75 und 1.00. Diese generierten die fünf als kleinere Punkte im Diagramm dargestellten Punkte. Ich werde die verwendeten Gleichungen darstellen, wenn ich den Code für die AddBezierCurve-Funktion erläutere. Sie sehen, dass der erste Bézier-Punkt für t = 0.0 (1,2) = P0 ist und dass der letzte Bézier-Punkt für t = 1.0 (13,8) = P2 ist. Werte von t = 0.0 und t = 1.0 generieren im Allgemeinen die Punkte P0 und P2. Wenn wir die Bézier-Punkte mit Liniensegmenten verbinden, wird eine Kurve zwischen P0 und P2 approximiert. Weitere Werte von t generieren weitere Punkte, die eine weichere Kurvenapproximation erzeugen.

Die Endpunkte P0 und P2 vorausgesetzt, bestimmt der Wert von P1 die Form der sich ergebenden Bézier-Kurve. Im Beispiel in Abbildung 2 habe ich willkürlich einen Punkt ausgewählt, der in der Mitte zwischen P0 und P2 auf der horizontalen Achse und ein klein wenig oberhalb des höchsten Punktes (P2) auf der vertikalen Achse liegt. Wenn ich P1 ein wenig nach links verschoben hätte, würde sich die Kurve nach links bewegen und symmetrischer werden. Wenn ich die Höhe von P1 angehoben hätte, wäre die Kurve höher und spitzer.

Aufrufen der Funktion „AddBezierCurve“

Aus Abbildung 1 wird ersichtlich, dass sich meine Karte auf einer Webseite namens „CurveDemo.html“ befindet (steht über den Codedownload zur Verfügung). Die allgemeine Struktur von CurveDemo.html sehen Sie in Abbildung 3.

Abbildung 3 Struktur der Demo-Webseite

    <html>
    <!-- CurveDemo.html -->
    <head>
    <title>Bing Maps AJAX Bezier Curve</title>
    <script type="text/javascript"
     src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3">
    </script>
    <script type="text/javascript">
    
     var map = null; // global VEMap object
    
     function AddBezierCurve(start, finish, arcHeight, skew, color, width,
      upDown, numSegments)
     {
       // Code here
     }
    
     function MakeMap()
     {
      map = new VEMap('myMap');
      map.LoadMap(new VELatLong(39.8600, -105.0000), 10, VEMapStyle.Road);
      var start = new VELatLong(40.0200, -105.2700); // Boulder
      var finish = new VELatLong(39.9000, -104.7000); // airport
      var arcHeight = 0.20;
      var skew = -0.004;
      var color = new VEColor(0,0,255,1.0); // blue
      var width = 6;
      var numSegments = 200;
    
      AddBezierCurve(start, finish, arcHeight, skew, color, width, 'up', numSegments);
     }
    
    </script>
    </head>               
    <body onload="MakeMap();">
    <div id='myMap' style="position:relative; width:800px; height:600px;"></div>
    </body>
    </html>

Nach dem HTML-Tag „<title>“ verwende ich das Element „<script>“, um Programmzugriff auf die Version 6.3 der Bing Maps AJAX-Kartensteuerelementbibliothek zu erhalten. Beachten Sie, dass ich keine Praktiken guter Coderstellung anwende, wie z. B. die Einbeziehung der DOCTYPE-Deklaration, um die Größe meines Democodes zu begrenzen. Zum Zeitpunkt der Niederschrift dieses Artikels ist die aktuelle Version der Bibliothek die Version 7. Diese verfügt über eine verbesserte Leistung, doch die Codebasis unterscheidet sich völlig von früheren Versionen. Wenn Sie AddBezierCurve nicht mit einem vorhandenen Bing Maps AJAX API-Satz verwenden, sollten Sie vielleicht die Version 7 verwenden. Es sollte für Sie möglich sein, den von mir hier dargestellten Code mit wenig Mühe für die Version 7 von Bing Maps AJAX umgestalten zu können.

Ich deklariere ein globales VEMap-Objekt mit dem Namen „map“, und instanziiere es mit Null. Beachten Sie, dass JavaScript keine expliziten Typdeklarationen verwendet und es ohne einen Kommentar deshalb nicht offensichtlich ist, dass „map“ vom Typ „VEMap“ ist.

Die AddBezierCurve-Funktion akzeptiert bis zu acht Parameterwerte. Die ersten vier Parameter („start“, „finish“, „arcHeight“ und „skew“) sind erforderlich. Die folgenden vier Parameter („color“, „width“, „upDown“ und „numSegments“) sind optional und haben Standardwerte. Die Parameter „start“ und „finish“ sind Objekte vom Typ „VELatLong“ (geografische Breite und Länge) und repräsentieren die im vorangegangenen Abschnitt beschriebenen Endpunkte P0 und P2. Der arcHeight-Parameter repräsentiert die Höhe des dazwischenliegenden, die Form bestimmenden Punktes P1. Der skew-Parameter repräsentiert die Links-Rechts-Anpassung des Punktes P1. Der color-Parameter ist die VEColor der Kurve. Der width-Parameter ist ein numerischer Wert, der die Breite der Kurve angibt. Der upDown-Parameter ist eine Zeichenfolge, die „up“ oder „down“ sein kann und angibt, ob die Kurve sich nach oben oder nach unten biegen soll. Der numSegments-Parameter gibt die Anzahl der für den Bézier-Wert t zu verwendenden Werte an, der wiederum bestimmt, wie viele Liniensegmente die Kurve bilden.

Die MakeMap-Funktion instanziiert das VEMap-Objekt mit dem new-Schlüsselwort und legt die Steuerelement-ID auf „myMap“ fest. Dadurch kann der Webbrowser mithilfe der AJAX-Antwort interpretieren, an welcher Stelle in CurveDemo.html das Kartensteuerelement platziert werden soll. Ich setzte die LoadMap-Methode ein, um die Karte zunächst zentriert ein wenig nördlich von Denver zu positionieren – mit einer Zoomstufe von 10 – und verwende die Road-Ansicht. Anschließend lege ich den start-Parameter auf die geografische Breite und Länge von Boulder und den finish-Parameter auf ein Gebiet ein wenig nördlich vom Denver International Airport fest. Ich lege „arcHeight“ auf 0.20 fest. Wie wir im nächsten Abschnitt sehen werden, wird „arcHeight“ als Breitengrad über dem Mittelwert zwischen P0 (start) und P2 (finish) interpretiert. Ich lege „skew“ auf -0.004 fest, um die Aufwölbung der Kurve um 0.004 Längengrade leicht nach links zu verschieben. Der color-Parameter wurde auf Blau ohne Alpha-Transparenz festgelegt, die Breite der gekrümmten Linie auf 6 und die Anzahl der Liniensegmente auf 200. Anschließend wird die AddBezierCurve-Funktion mit einer „up“-Richtung aufgerufen.

Im HTML-Tag „body“ verwende ich das onload-Ereignis, um die MakeMap-Funktion aufzurufen, die wiederum AddBezierCurve aufruft.

Definieren der Funktion „AddBezierCurve“

Die Funktion „AddBezierCurve“ beginnt folgendermaßen:

function AddBezierCurve(start, finish, arcHeight, skew, color, width,
 upDown, numSegments)
{
  // Preconditions and parameter descriptions here
  if (typeof color == 'undefined') { var color = new VEColor(255,0,0,1.0); }
  if (typeof width == 'undefined') { var width = 2; }
  if (typeof upDown == 'undefined') { var upDown = 'up'; }
  if (typeof numSegments == 'undefined') { var numSegments = 10; }
  ...

Um Platz zu sparen, habe ich die beschreibenden Kommentare zu den angenommenen Vorbedingungen der Funktion (z. B. das Vorhandensein eines instanziierten globalen VEMap-Objektes namens „map“) und die Beschreibungen der acht Eingabeparameter entfernt. Am Anfang des Codes der Funktion werden die Standardparameter definiert. Ich verwende den JavaScript-Operator „typeof“ zur Bestimmung, ob die Parameter „color“, „width“, „upDown“ und „numSegments“ vorhanden sind. Der Operator „typeof“ gibt die Zeichenfolge „undefined“ und nicht „null“ zurück, wenn eine Variable nicht vorhanden ist. Wenn der color-Parameter fehlt, erstelle ich ein VEColor-Objekt für den lokalen Bereich namens „color“ und instanziiere es mit „red“ (die Parameter für VEColor sind „red“, „green“, „blue“ und „transparency“). Mit demselben Ansatz erstelle ich Standardwerte für „width“ (2), „upDown“ („up“) und „numSegments“ (10).

Die Funktion geht folgendermaßen weiter:

if (start.Longitude > finish.Longitude) {   
 var temp = start;
 start = finish;
 finish = temp;
}

if (numSegments < 2)
 numSegments = 2;
...

Ich normalisiere die VELatLong-Parameter „start“ und „finish“, sodass der Startpunkt links vom Endpunkt liegt. Aus technischer Sicht ist das nicht notwendig, der Code wird dadurch jedoch verständlicher. Ich führe eine Fehlerüberprüfung für den numSegments-Parameter durch, um sicherzustellen, dass mindestens zwei Liniensegmente für die Ergebniskurve vorhanden sind.

Als Nächstes berechne ich die Koordinaten eines Punktes, der in der Mitte zwischen P0 (start) und P2 (finish) auf der Linie liegt, die P0 und P2 miteinander verbindet:

var midLat = (finish.Latitude + start.Latitude) / 2.0;
var midLon = (finish.Longitude + start.Longitude) / 2.0;
...

Dieser Punkt wird als Ausgangspunkt dienen, um den dazwischenliegenden Punkt P1 mithilfe der Parameter „arcHeight“ und „skew“ zu erstellen.

Anschließend bestimme ich P1 wie folgt:

if (Math.abs(start.Longitude - finish.Longitude) < 0.0001) { 
 if (upDown == 'up')
   midLon -= arcHeight;
 else
   midLon += arcHeight;
 midLat += skew;
}
else { // 'normal' case, not vertical
 if (upDown == 'up')
   midLat += arcHeight;
 else
   midLat -= arcHeight;
 midLon += skew;
}
...

Ich werde gleich den ersten Teil der Codelogik erklären. Der zweite Teil der Verzweigungslogik ist der Normalfall. Hier ist die Linie, die den Anfangs- und den Endpunkt verbindet, vertikal. In diesem Fall prüfe ich den Wert des upDown-Parameters. Wenn dieser „up“ ist, füge ich den arcHeight-Wert dem Wert der geografischen Breite (up-down) der Basisreferenz des Mittelpunktes hinzu. Anschließend füge ich den skew-Wert dem Wert der geografischen Länge (left-right) der Basisreferenz hinzu. Wenn der upDown-Parameter nicht „up“ ist, nehme ich an, dass upDown „down“ ist und ziehe „arcHeight“ von der Komponente der geografischen Breite „up-down“ der Basisreferenz des Mittelpunktes ab. Beachten Sie, dass ich, anstatt einen expliziten upDown-Parameter zu verwenden, „upDown“ völlig löschen und einfach folgern könnte, dass positive Werte von „arcHeight“ „up“ bedeuten und negative Werte von „arcHeight“ „down“.

Der erste Teil der Verzweigungslogik befasst sich mit den vertikalen oder nahezu vertikalen Linien. Hier tausche ich im Endeffekt die Rollen des arcHeight-Wertes „up-down“ und verzerre den Wert für „left-right“. Beachten Sie, dass es keinen leftRight-Parameter für die Verzerrung gibt. Positive Werte für „skew“ bedeuten „nach rechts“ und negative Werte „nach links“.

Anschließend gebe ich den Algorithmus für die Generierung der Bézier-Kurve wie folgt ein:

var tDelta = 1.0 / numSegments;

var lons = new Array(); // 'x' values
for (t = 0.0; t <= 1.0; t += tDelta) {
var firstTerm = (1.0 - t) * (1.0 - t) * start.Longitude;
 var secondTerm = 2.0 * (1.0 - t) * t * midLon;
 var thirdTerm = t * t * finish.Longitude;
 var B = firstTerm + secondTerm + thirdTerm;
 lons.push(B);
}
...

Zunächst berechne ich die Differenz zwischen den t-Werten. Wie im vorangegangenen Abschnitt bereits erläutert, reichen die t-Werte von 0.0 bis einschließlich 1.0. Bei numSegments = 3 wäre tDelta also 0.33, und meine t-Werte wären 0.00, 0.33, 0.67 und 1.00, die vier Bézier-Punkte und drei Liniensegmente ergeben. Als Nächstes erstelle ich ein neues Array namens „lons“ für die x-Werte, die die Längengrade darstellen. Eine genaue Erläuterung der Bézier-Gleichungen würde den Rahmen dieses Artikels sprengen. Beachten Sie aber, dass es drei Terme gibt, die von den Werten für t, P0 (start), P1 (midLon) und P2 (finish), abhängig sind. Bézier-Kurven sind wirklich interessant, und es gibt im Internet viele Informationen dazu.

Anschließend verwende ich dieselben Bézier-Gleichungen, um die y-Werte (latitude) in einem Array namens „lats“ zu berechnen:

var lats = new Array(); // 'y' values
for (t = 0.0; t <= 1.0; t += tDelta) {
  var firstTerm = (1.0 - t) * (1.0 - t) * start.Latitude;
  var secondTerm = 2.0 * (1.0 - t) * t * midLat;
  var thirdTerm = t * t * finish.Latitude;
  var B = firstTerm + secondTerm + thirdTerm;
  lats.push(B);
}
...

Nun beende ich die AddBezierCurve-Funktion folgendermaßen:

var points = new Array();
 for (i = 0; i < lats.length; ++i) {
  points.push(new VELatLong(lats[i], lons[i]));
 }

 var curve = new VEShape(VEShapeType.Polyline, points);
 curve.HideIcon();
 curve.SetLineColor(color);
 curve.SetLineWidth(width);
 map.AddShape(curve);
}

Ich erstelle ein Array von VELatLong-Objekten namens „points“ und füge ihm die Breitengrad-Längengrad-Paare aus den Arrays „lats“ und „lons“ hinzu. Danach instanziiere ich ein VEShape vom Typ „Polyline“, blende das ärgerliche Standardsymbol aus, lege die Farbe und die Breite der Polyline fest und platziere mithilfe der AddShape-Methode die Bézier-Kurve auf dem globalen VEMap-Objekt namens „map“.

Testen der Funktion „AddBezierCurve“

Das Testen der AddBezierCurve-Funktion ist nicht trivial. Die Funktion verfügt über Objektparameter („start“, „finish“ und „color“), numerische Parameter („arcHeight“, „skew“, „width“ und „numSegments“) und einen Zeichenfolgenparameter („upDown“). Tatsächlich verwenden einige meiner Kollegen ähnliche Funktionen als Grundlage für Fragen bei Vorstellungsgesprächen zu Positionen im Softwaretestbereich. Diese Form des Testens wird häufig als „API-Test“ oder „Modultest“ bezeichnet. Als Erstes muss die Basisfunktionalität geprüft werden oder anders ausgedrückt: Macht die Funktion das, was sie in mehr oder weniger normalen Situationen machen soll? Anschließend schaut sich ein guter Tester zunächst die Funktionsparameter an und untersucht, was im Fall von ungültigen oder Grenzfall-Eingaben geschieht.

Die AddBezierCurve-Funktion führt keine anfängliche Überprüfung der VELatLong-Parameterwerte „start“ und „finish“ durch. Ist mindestens einer davon „null“ oder „undefined“, wird die Karte gerendert, es wird jedoch keine gekrümmte Linie angezeigt. Ebenso prüft die Funktion nicht, ob „start“ oder „finish“ unzulässige Werte haben. VELatLong-Objekte verwenden das Koordinatensystem vom World Geodetic System 1984 (WGS 84), bei dem zulässige Breitengrade im Bereich [-90.0, +90.0] und zulässige Längengrade im Bereich [-180.0, +180.0] liegen. Unzulässige Werte können dazu führen, dass AddBezierCurve unerwartete und falsche Ergebnisse ausgibt. Eine weitere Möglichkeit einer ungültigen Eingabe von Parameterwerten für „start“ und „finish“ sind Objekte vom falschen Typ, z. B. ein VEColor-Objekt.

Erfahrene Tester prüfen auch die numerischen Parameter „arcHeight“ und „skew“ auf ähnliche Weise. Interessante Grenzbedingungswerte für diese Parameter umfassen 0.0, -1.0 +1.0 sowie 1.7976931348623157e+308 (die in JavaScript größte Zahl auf vielen Systemen). Ein gründlicher Tester untersucht, welche Auswirkungen die Verwendung von Zeichenfolgen- oder Objektwerten für „arcHeight“ und „skew“ hat.

Das Testen des color-Parameters ist ähnlich wie bei den start- und finish-Parametern. Sie werden jedoch auch den Standardwert prüfen wollen, indem Sie den color-Parameter im Funktionsaufruf auslassen. Beachten Sie, dass JavaScript Parameter gemäß Position übergibt. Wenn Sie also den color-Parameterwert auslassen, sollten Sie ebenfalls alle nachfolgenden Parameterwerte („width“, „upDown“ und „numSegments“) auslassen. Abgesehen davon, würde das Auslassen von „color“ bei gleichzeitigem Bereitstellen von Werten für mindestens einen der nachstehenden Parameter zur Fehlausrichtung von Parameterwerten führen. Dies würde ein erfahrener Tester untersuchen.

Da die Parameter „width“ und „numSegments“ physikalische Messwerte repräsentieren, würden gute Tester mit Sicherheit Werte von 0 und negative Werte für „width“ und „numSegments“ prüfen. Da diese Parameterwerte als Ganzzahlen implizit sind, möchten Sie vielleicht auch nicht ganzzahlige numerische Werte wie 3.5 prüfen.

Bei genauerer Betrachtung des Codes von „AddBezierCurve“ wird Ihnen auffallen, dass wenn der Wert des upDown-Parameters anders als „up“ ist, die Funktion den Parameterwert als „down“ interpretiert. Daraus folgernd würde sich ein erfahrener Tester fragen, wie das ordnungsgemäße Verhalten von Null, einer leeren Zeichenfolge und einer Zeichenfolge mit einem einzelnen Leerzeichen ist. Darüber hinaus sollten numerische Werte und Objektwerte für „upDown“ getestet werden. Ein erfahrener Tester fragt möglicherweise den Entwickler, ob es beabsichtigt ist, dass der upDown-Parameter zwischen Groß- und Kleinschreibung unterscheidet, so wie es jetzt ist. Ein Wert von „UP“ für „upDown“ wird als „down“ interpretiert.  

API-Funktionen eignen sich häufig gut für automatische Tests mit Zufallseingaben. Sie können mit einer der vielen Techniken zufällige Werte für „start“, „finish“, „arcHeight“ und „skew“ programmgesteuert generieren, diese Werte an die Webseite senden und prüfen, ob Ausnahmen ausgelöst werden.

Zweifache Zielsetzung

Zusammengefasst verfolgt dieser Artikel zwei Absichten. Zum einen werden eine praktische JavaScript-Funktion, die dem Zeichnen einer Bézier-Kurve auf einem Bing Maps AJAX-Kartensteuerelement dient, sowie die zugrundeliegende Codelogik dargestellt. Sollten Sie jemals mit Karten arbeiten, werden Sie die hier dargestellte AddBezierCurve-Funktion wohl als eine sehr nützliche Ressource betrachten. Zum anderen verfolgt dieser Artikel den Zweck, Richtlinien zum Testen einer nicht trivialen JavaScript-Funktion darzustellen. Es wurde verdeutlicht, dass es eine Reihe von Dingen gibt, die geprüft werden sollten. Dazu gehören Null-Werte oder fehlende Werte, unzulässige Werte, Begrenzungswerte und Werte mit fehlerhaftem Typ. Diese Prinzipien gelten für in den meisten Programmiersprachen geschriebene API-/Modultestfunktionen.      

Dr. James McCaffrey ist für Volt Information Sciences Inc. tätig. Er leitet technische Schulungen für Softwareentwickler, die auf dem Campus von Microsoft in Redmond, USA arbeiten. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem an Internet Explorer und MSN Search. Dr. McCaffrey ist der Autor von ".NET Test Automation Recipes" (Apress 2006) und kann unter jammc@microsoft.com erreicht werden.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Paul Koch, Dan Liebling, Anne Loomis Thompson und Shane Williams