Verarbeitung¶
Ein Wettbewerb benötigt eine Auswertung, eine Evaluation. Diese besteht aus drei Javascript Funktionen, welche den Lebenszyklus des Wettbewerbs beschreiben. Um deren Funktionsweise zu verstehen ist es sinnvoll, zuerst die grundlegende Funktionsweise zu verstehen.
Grundlagen¶
Während eines Wettbewerbs liefern die Geräte jedes Teilnehmers einen endlosen Strom von Geräte-Events, welche zum Sport-Server gesendet werden. Ein Teilnehmer ist also aus Sicht des Systems nicht anderes als ein Datensatz mit Namen, Avatar, … und vor allem: Einen endlosen Strom von Wegpunkten.
Alleine die Zeitmessung legt fest, welche dieser Wegpunkte relevant sind und welche ignoriert werden können. Es gibt mehrere Zeitstempel, welche wichtig sind:
- Der ganze Wettbewerb hat einen Beginn- und Ende-Zeitstempel. Nur Ereignisse die in diesem Zeitraum liegen sind überhaupt relevant.
- Jeder Teilnehmer selbst wird mehrere Zeitmessungen unterzogen. Nur Ereignisse, die während einer aktiven Messung des Teilnehmers erfolgen, sind auch relevant.
Zu jedem Wettbewerb wird pro Teilnehmer ein Ranking-Datensatz gespeichert. Dieser Datensatz wird bei jeden eingehenden Event, welcher während einer Zeitmessung erfolgt neu berechnet. Welche Daten in diesem Datensatz gespeichert werden ist vollkommen frei vom Entwickler des Reducers zu entscheiden. Am Anfang des Wettbewerbs (bzw. bei der ersten Zeitmessung) wird ein initialer Datensatz angelegt und bei jeden eingehenden Geräte-Event wird dieser Datensatz geändert.
In regelmässigen Abständen evaluiert der Sports-Server diese Datensätze um ein Ranking zu errechnen und dieses Ranking anzuzeigen.
Ein Wettbewerb sieht also so aus:
- Initialisierung eines Datensatzes pro Teilnehmer
- Pro eingehenden Geräte-Event wird dieser Datensatz mutiert indem eine benutzerdefinierte Funktion aufgerufen wird, welcher der aktuelle Datensatz und der aktuelle Event als Parameter übergeben werden.
- In regelmässigen Abständen/Intervallen wird ein neues Ranking berechnet und publiziert
Code¶
Im folgenden soll ein Beispielcode entwickelt werden, welcher als Grundlage dienen kann um eigene Reducer zu entwickeln. Das zu entwickelnde System soll ein Ranking darstellen, welches den Teilnehmer mit der höchsten Geschwindigkeit zum Gewinner kürt.
Initializierung¶
Die Initialisierungsfunktion muss den Namen initValue
besitzen und eine Datenstruktur
zurückgeben, welche als Initialwert für einen Teilnehmer dient.
1 2 3 4 5 |
|
Der Code liefert ein Objekt zurück, welches das Feld maxspeed
bereitstellt und mit dem
Wert 0
initialisiert, d.h. für jeden Teilnehmer wird ein Datensatz der Form
1 2 3 |
|
Reducer¶
Liefert das Gerät des Teilnehmers nun ein Signal, so wird die Funktion reduce
aufgerufen. Als Paramter bekommt diese Funktion den aktuellen Datensatz data
und das
Geräte-Signal event
als Parameter.
Während die Struktur des data
Parameter dem Entwickler bekannt sein sollte (der
Initialwert wird ja mit der initValue
erzeugt), hat ein Event folgende Struktur:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Um bei der Rangliste den Teilnehmer mit der höchsten Geschwindigkeit als Gewinner zu
küren, muss die reduce
Funktion sich die höchste Geschwindigkeit merken:
1 2 3 4 5 6 7 8 9 |
|
Es wird hier nur geprüft, ob die im Geräte-Datensatz enthaltene Geschwindigkeit
grösser ist, als die im Teilnehmer-Datensatz gespeicherte. Wenn dem so ist, wird diese
Geschwindigkeit als neue maximale Geschwindigkeit gespeichert. Die Funktion jslog
kann
genutzt werden um Logausgaben zu erzeugen, welche in der Sports-App angezeigt bzw.
verfolgt werden können.
Jeder Datensatz, welcher vom Gerät gesendet wird, wird mit Hilfe der reduce
Funktion auf
einen Teilnehmer-Datensatz reduziert, d.h. der eingehende Strom von Events wird auf einen einzigen
Datensatz komprimiert. In diesem einfachen Beispiel ist es einfach die maximale
Geschwindigkeit, aber natürlich sind hier endlos viele Möglichkeiten denkbar.
Ranking¶
Letzendlich wird jedoch ein Rangliste benötigt, welche von der Sports-App auch sortiert
werden muss. Damit diese Sortierung und Darstellung unabhängig von der benutzerdefinierten
Datenstruktur erfolgt muss noch eine dritte Funktion zur Verfügung gestell werden, die
rankValue
.
1 2 3 4 5 6 7 |
|
Diese Funktion liefert einen Triple mit drei ganzen Zahlen zurück. Die Sortierung der Teilnehmer erfolgt dann in der Art, dass derjenige mit dem höchsten Score am Index 0 der Gewinner ist. Gibt es mehrere Teilnehmer mit dem gleichen Wert, wird nach dem Index 1 sortiert und ist auch dieser gleich, wird der Index 2 verwendet. Sind alle drei Werte gleich, so gelten die Teilnehmer als punktgleich.
Im obigen Beispiel wird die gespeicherte Geschwindigkeit mit 100 multipliziert, da es sich
um eine Kommazahl handelt, welche die Geschwindigkeit in km/h angibt. Dieser Wert wird
dann (mit der JS-Standardfunktion parseInt
) zu einem Integer umgewandelt. Die beiden
anderen Werten werden als 0
zurückgeliefert und können in diesem Fall auch weggelassen
werden, d.h. es würde auch reichen einfach nur einen Array mit einem einzigen Element
zurückzugeben.
Debugger¶
Zur Entwicklung der reducer
und ranking
Funktionalitäten bietet das System einen
integrierten Debug-Mechanismus bereit. Dazu können die Funktionen mit fest hinterlegten
Events durchgespielt werden. Jedes Event kann als Einzelschritt oder automatisch
abgefeuert werden.
Generator¶
Grundlage für den Debugger ist ein Generator. Hier handelt es sich um eine Entität welche einen Strom von Events erzeugen kann. Es gibt hierzu drei verschiedene Generator-Typen:
-
Stringbasiert
In einem Edit-Bereich wird ein JSON-Array eingegeben, welcher dem Event-Format der Plattform entspricht. Es kann zwar die Syntax geprüft werden; ob jedoch auch die gewünschten Events erzeugt werden, zeigt sich erst zur Laufzeit. -
Datenbankbasiert
Es können auch Events direkt aus der Datenbank selektiert werden. Hierzu werden zwei Zeitstempel (Begin, Ende) sowie die Quelle (Comeptition
,Team
,Participant
) ausgewählt. Das System holt sich die entsprechenden Events und speichert diese redundant in zeitlicher Reihenfolge sortiert. Achtung: Bei grossen Zeiträumen kann das dazu führen, dass es länger dauert bis alle Daten gepuffert sind. Ausserdem wird viel Platz in der Datenbank belegt, d.h. grosse DB-Generatoren sollten nur für Replay’s genutzt und dann wieder gelöscht werden. -
GPX basiert
Hier können verschiedene GPX-Daten hochgeladen werden aus dem Tracking-Daten extrahiert werden. Zu jeder Daten wird noch eine IMEI sowie ein Zeitstempel und ein Interval benötigt. Das System erzeugt dann aus den Positionen, der IMEI und den Zeitangaben eine Liste von Events. Wenn gewünscht, kann diese Liste dann in einen String-basierten Generator umgewandelt werden, so dass dort dann einzelne Attribute der Events noch manuell angepasst werden können.
Ist ein passender Generator angelegt, so kann dieser zum Debuggen einer Evaluation
genutzt werden.
Debuggerdaten¶
Der Debugger erwartet drei Felder, die befüllt werden können/müssen:
- Start Zeitstempel
In einemreducer
kann auf sogenanntemeasurements
zugegriffen werden. Das sind Zeitmessungen, die beim Starten der Messung angelegt werden. Benötigt derreducer
ein Measurement, so kann durch die Angabe des Zeitstempels eine Zeitmessung angelegt werden, die dann im Code abgefragt werden kann. Benötigt der Code dieses nicht, kann das Feld leer gelassen werden. - Competition
In der Liste werden alle aktuell verfügbaren Competitions angezeigt. Der Debugger benötigt diese um ein passenden Ranking für alle Teilnehmer anzulegen, welches dann vomreducer
mit Daten befüllt wird. - Generator
Der Generator liefert einen Strom von Events.
Initialisierung¶
Das starten der Debug-Session erfolgt mit einem Klick auf Reset
Das Sytem lädt die Events aus dem Generator und zeigt einen Ausschnitt daraus an. Der gelb hinterlegte ist immer der Datensatz, welcher als nächster zur Ausführung kommt.
Mit den Knöpfen 1x
, 2x
… kann ein, zwei, … Events gegen den Reducer ausgeführt
werden.
Direkt unter der Liste der Events ist ein Ausgabebereich für die Logs des reducer
s und
darunter sind die Laufzeitdaten der Competition
. Durch die Selektion eines Teilnehmers in
der Dropdown Liste kann die Anzeige der Daten auf diesen Teilnehmer beschränkt werden. Mit
dem PLUS
Knopf können mehrere solcher Anzeigebereiche angelegt werden bei dem jeder ggf.
einen spezifischen Teilnehmer anzeigt.
Ist eine Debug-Session durchgelaufen (alle Events), so kann diese durch Reset
erneut
gestartet werden.
Schrittweises Debuggen¶
Nach einem Debug-Schritt, zeigt die Liste der Events den neuen (zukünftigen) Record gelb, die Ausgabe zeigt eventuell erfolgte Logs und der Datenbereich zeigt den Datenbestand der selektierten Teilnehmer:
Der Datenbereich besteht immer aus einer Struktur mit folgenden Werten:
-
value1
…value3
Diese Werte sind die Ergebnisse der Rank-Funktion -
userdata
Im Benutzerdatenbereich befinden sich die Daten, welche der Reducer nach jedem Schritt zurückliefert.
Autoplay¶
Mit Hilfe des automatischen debuggens können die Events aus dem Generator in zeitlicher
Abfolge durschritten werden. Achtung: Die Events beinhalten alle einen Sent
Zeitstempel,
d.h. die Geschwindigkeit mit der die Events abgearbeitet werden sollte die Logik des
Reducers nicht beeinflussen.
Wenn der Debugger im Automatikmodus läuft kann jederzeit auf auf den Pause-Knopf gedrückt werden und dann im Einzelschrittmodus weigergemacht werden.
Bereitgestelte Objekte¶
Wie im Beispiel zu sehen ist, wird der reducer
Funktion immer der aktuelle Datensatz und
der aktuelle Event übergeben. Neben diesen Daten stehen in der Funktion aber noch andere
Daten zur Verfügung.
Logging¶
So gibt es eine Funktion jslog
mit welcher Logausgaben durchgeführt werden können. Die
Signatur der Funktion lautet:
1 |
|
Der erste Parameter ist also eine Logmessage, danach erfolgen immer Key/Value Paare, wobei der Key ein String sein muss und der Value frei definierbar ist. Im obigen Beispiel war folgender Aufruf enthalten:
1 |
|
Hier ist new maxspeed found
die Logmessage; dann folgen die Key/Value Paare ("event",
event)
und ("data",data)
.
Mapdaten¶
Grundlegende Daten zur angezeigten Map sind in der Variablen mapdata
enthalten.
mapdata.name
Der Name des Wettbewerbsmapdata.baselayer
Der Name des eingestellten Layersmapdata.opacity
Der Wert der eingestellten Deckkraftmapdata.zoomlevel
Der Wert des eingestellten Zoomlevelsmapdata.location
Die Position des Wettbewerbsmarkers
Ausserdem enthält mapdata
noch jeweils die gespeicherten Kurse und Regionen mit den
Namen, welche im System angegeben wurden. Ein Kurs ist eine Sammlung von Linien und der
Kurs besitzt eine Funktion intersects
, bspw.
mapdata.Finish.intersects(pos1, pos2)
Wenn es einen Kurs mit dem NamenFinish
gibt, kann mit dieser Funktion erfragt werden, ob sich diese Kurs mit einer Verbindungslinie aus den beiden übergebenen Positionen schneidet (Es können auch direkt Events übergeben werden; das System extrahiert dort selbständig die Position).
Bei einer Region gibt es eine Funktion contains
welcher nur eine einzige Position (bzw.
ein Event) übergeben wird. Ist diese Position in der Region enthalten, so liefert diese
Funktion den Wert true
.
Messung¶
Ein Teilnehmer kann mehrere Zeitmessungen haben. Um diese zu erfragen, dient das
measurement
Objekt. Dieses stellt eine Funktion getAt
zur Verfügung, welche die
Zeitmessung für den angegebenen Geräte-Event liefert.
1 2 |
|
Dieser Code fragt das System nach dem Beginn der Zeitmessung für das aktuelle
Geräte-Event. Das Attribut start
ist ein Javascript-Daten Objekt mit dem Zeitstempel.
Dieses kann genutzt werden, wenn ein Rennen bspw. mit einem fest definierten Massenstart
und einem klaren Signal gestartet wird.
Race¶
Über das race
Objekt ist es möglich, die Zeitmessung zu stoppen; dazu dient die Funktion
stop(evt)
die als Parameter einen Event benötigt. Wird diese Funktion aufgerufen, wird
die Zeitmessung für das im Event enthaltene Geräte beendet und der sent
Zeitstempel als
Ende der Zeitmessung genutzt. Bsp.:
1 2 3 4 |
|