Skip to content

Processing

A competition requires an evaluation, an evaluation. This consists of three Javascript functions that describe the life cycle of the competition. To understand how they work, it is useful to first understand the basic functionality.

Basics

During a competition, each competitor’s devices deliver an endless stream of device events that are sent to the sports server. From the system’s point of view, a participant is therefore nothing more than a data record with a name, avatar, … and above all: an endless stream of waypoints.

The time measurement alone determines which of these waypoints are relevant and which can be ignored. There are several time stamps that are important:

  • The entire competition has a start and end timestamp. Only events that lie within this period are relevant at all - Each participant is subjected to several time measurements. Only events that take place during an active measurement of the competitor are relevant.

A ranking data record is saved for each participant for each competition. This data record is recalculated for each incoming event that takes place during a time measurement. The developer of the reducer is completely free to decide which data is saved in this data record. An initial data set is created at the start of the competition (or at the first time measurement) and this data set is changed for each incoming device event.

The sports server evaluates these data sets at regular intervals to calculate a ranking and display this ranking.

A competition looks like this:

  • Initialisation of a data set per participant - For each incoming device event, this data set is mutated by calling a user-defined function to which the current data set and the current event are passed as parameters - A new ranking is calculated and published at regular intervals

Code

In the following, an example code is to be developed which can serve as a basis for developing your own reducers. The system to be developed is intended to represent a ranking which selects the participant with the highest speed as the winner.

Initialisation

The initialisation function must have the name initValue and return a data structure which serves as the initial value for a participant.

1
2
3
4
5
function initValue() {
    return {
        maxspeed: 0
    }
}

The code returns an object which provides the field maxspeed and initialises it with the value 0, i.e. for each participant a data record of the form

1
2
3
{
  maxspeed: 0
}
angelegt.

Reducer

If the subscriber’s device now supplies a signal, the reduce function is called. This function receives the current data set data and the device signal event as parameters.

While the structure of the data parameter should be known to the developer (the initial value is generated with the initValue), an event has the following structure:

 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
{
  "imei": "123123123123",
  "sent": "2022-02-20T14:05:15.68896Z",
  "received": "2022-02-20T14:05:15.68896Z",
  "code": "POSITION",
  "deviceCode": 49,
  "deviceEvent": "",
  "position": {
    "invalid": false,
    "lat": 58.03461323789451,
    "lng": 21.576161484035282,
    "alt": 0,
    "height": 584,
    "gpsfix": 3,
    "numsatellites": 5,
    "course": 0,
    "speedkmh": 20,
    "verticalspeedms": 0,
    "motionless": 0,
    "hdop": 0,
    "vdop": 0
  },
  "battery": {
    "loadpercent": 87,
    "low": false,
    "loadvoltage": 0
  },
  "type": "alive",
  "source": "socket"
}

To select the participant with the highest speed as the winner in the ranking list, the ‘reduce’ function must memorise the highest speed:

1
2
3
4
5
6
7
8
9
function reduce(data, event) {
    // some devices can send events without a legal position, so use ?.
    const speed = event?.position?.speedkmh || 0;
    if (speed > data.maxspeed) {
        data.maxspeed = speed;
        jslog("new maxspeed found", "event", event, "data", data);
    }
    return data;
}

It is only checked here whether the speed contained in the device data record is greater than the speed saved in the subscriber data record. If this is the case, this speed is saved as the new maximum speed. The jslog function can be used to generate log outputs that can be displayed or tracked in the Sports app.

Each data set sent from the device is reduced to one participant data set using the reduce function, i.e. the incoming stream of events is compressed to a single data set. In this simple example it is simply the maximum speed, but of course there are endless possibilities here.

Ranking

Ultimately, however, a ranking list is required, which must also be sorted by the sports app. To ensure that this sorting and display is independent of the user-defined data structure, a third function must be made available, the rankValue.

1
2
3
4
5
6
7
function rankValue (data) {
    return [
        parseInt(data.maxspeed * 100, 10),
        0,
        0
    ]
}

This function returns a triple with three integers. The participants are then sorted in such a way that the one with the highest score at index 0 is the winner. If there are several participants with the same value, sorting is based on index 1 and if this is also the same, index 2 is used. If all three values are the same, the participants are considered to have the same score.

In the above example, the saved speed is multiplied by 100, as this is a decimal number that indicates the speed in km/h. This value is then converted to an integer (using the standard JS function parseInt). The other two values are returned as 0 and can also be omitted in this case, i.e. it would also be sufficient to simply return an array with a single element.

Debugger

The system provides an integrated debugging mechanism for developing the reducer and ranking functionalities. For this purpose, the functions can be played through with defined events. Each event can be fired as a single step or automatically.

Generator

The basis for the debugger is a generator. This is an entity that can generate a stream of events. There are three different generator types:

  • String-based
    A JSON array is entered in an edit area, which corresponds to the event format of the platform. The syntax can be checked, but whether the desired events are generated only becomes apparent at runtime.

  • Database-based
    Events can also be selected directly from the database. Two time stamps (start, end) and the source (Comeptition, Team, Participant) are selected for this. The system retrieves the corresponding events and stores them redundantly in chronological order. Caution: With large time periods, this can lead to it taking longer for all data to be buffered. In addition, a lot of space is taken up in the database, i.e. large DB generators should only be used for replays and then deleted again.

  • GPX based
    Various GPX data can be uploaded here from which tracking data can be extracted. An IMEI, a timestamp and an interval are required for each piece of data. The system then generates a list of events from the positions, the IMEI and the time information. If desired, this list can then be converted into a string-based generator, so that individual attributes of the events can be adjusted manually.

Once a suitable generator has been created, it can be used to debug an evaluation.

Debugger data

The debugger expects three fields that can/must be filled:

Metadata

  • Start timestamp
    So-called measurements can be accessed in a reducer. These are time measurements that are created when the measurement is started. If the reducer requires a measurement, a time measurement can be created by specifying the timestamp, which can then be queried in the code. If the code does not require this, the field can be left empty. - Competition
    All currently available competitions are displayed in the list. The debugger needs these to create a suitable ranking for all participants, which is then filled with data by the reducer. - Generator
    The generator delivers a stream of events.

Initialisation

The debug session is started by clicking on Reset

Initialisation

The system loads the events from the generator and displays a section of them. The data set highlighted in yellow is always the next one to be executed.

With the buttons 1x, 2x… one, two, … events can be executed against the reducer.

Directly below the list of events is an output area for the logs of the reducer and below this is the runtime data of the competition. By selecting a participant in the drop-down list, the display of the data can be restricted to this participant. The PLUS button can be used to create several such display areas, each of which may display a specific participant.

Once a debug session has run through (all events), it can be restarted by pressing Reset.

Step-by-step debugging

After a debug step, the list of events shows the new (future) record in yellow, the output shows any logs and the data area shows the data of the selected participants:

Debug step

The data area always consists of a structure with the following values:

  • value1value3
    These values are the results of the rank function

  • userdata
    The user data area contains the data that the reducer returns after each step.

Autoplay

With the help of automatic debugging, the events from the generator can be processed in chronological order. Attention: The events all contain a Sent timestamp, i.e. the speed at which the events are processed should not influence the logic of the reducer.

If the debugger is running in automatic mode, the pause button can be pressed at any time and then resumed in single-step mode.

Provided objects

As can be seen in the example, the current data set and the current event are always passed to the reducer function. In addition to this data, other data is also available in the function.

Logging

There is a function jslog with which log outputs can be carried out. The signature of the function is

1
function jslog(message, key1, value1, key2, value2, ...) 

The first parameter is therefore a log message, followed by key/value pairs, whereby the key must be a string and the value is freely definable. The example above contained the following call:

1
jslog("new maxspeed found", "event", event, "data", data);

Here new maxspeed found is the log message; then follow the key/value pairs ("event", event) and ("data",data).

Map data

Basic data on the displayed map is contained in the variable mapdata.

  • mapdata.name
    The name of the competition - mapdata.baselayer
    The name of the set layer - mapdata.opacity
    The value of the set opacity - mapdata.zoomlevel
    The value of the set zoom level - mapdata.location
    The position of the competition marker

In addition, mapdata also contains the saved courses and regions with the names that were specified in the system. A course is a collection of lines and the course has an intersects function, e.g. mapdata.finish.intersects.

  • mapdata.Finish.intersects(pos1, pos2)
    If there is a course with the name Finish, this function can be used to query whether this course intersects with a connecting line from the two transferred positions (events can also be transferred directly; the system automatically extracts the position there).

With a region, there is a function contains to which only a single position (or event) is passed. If this position is contained in the region, this function returns the value true.

Measurement

A participant can have several time measurements. The measurement object is used to request these. This provides a function getAt, which returns the time measurement for the specified device event.

1
javascript const m = measurement.getAt(event); let firstTS = m.start; 

This code asks the system for the start of the time measurement for the current device event. The start attribute is a javascript data object with the timestamp, which can be used if a race is started with a fixed mass start and a clear signal, for example.

Race

It is possible to stop the time measurement via the race object; the stop(evt) function, which requires an event as a parameter, is used for this purpose. If this function is called, the time measurement for the device contained in the event is ended and the sent timestamp is used as the end of the time measurement. Example

1
2
3
4
    // stop measurement if 10 laps are driven ...
    if (data.lapcount > 10) {
        race.stop(event)
    }