@rasifix/orienteering-utils 1.0.6 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -4
- package/index.d.ts +0 -4
- package/index.js +0 -11
- package/lib/anonymizer.js +0 -72
- package/lib/butterfly.js +0 -59
- package/lib/kraemer.js +0 -125
- package/lib/oware.js +0 -105
- package/lib/ranking.js +0 -422
- package/lib/reorganize.js +0 -212
- package/lib/solv.js +0 -124
- package/lib/time.js +0 -45
- package/test/butterfly-oware.csv +0 -120
- package/test/oware-test.js +0 -82
- package/test/oware.csv +0 -101
- package/test/ranking-test.js +0 -102
- package/test/solv-test.js +0 -68
- package/test/solv.csv +0 -1833
- package/test/test.js +0 -11
package/lib/ranking.js
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
const { parseTime, formatTime } = require('./time');
|
|
2
|
-
const { reorganize } = require('./reorganize');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function invalidTime(time) {
|
|
6
|
-
return !time || time < 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function validTime(time) {
|
|
10
|
-
return !invalidTime(time);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function sum(a1, a2) {
|
|
14
|
-
return a1 + a2;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function defineCourses(runners) {
|
|
18
|
-
let courses = { };
|
|
19
|
-
runners.forEach(runner => {
|
|
20
|
-
let course = defineCourse(runner);
|
|
21
|
-
if (!courses[course]) {
|
|
22
|
-
courses[course] = {
|
|
23
|
-
code: course,
|
|
24
|
-
runners: [ runner.id ]
|
|
25
|
-
};
|
|
26
|
-
} else {
|
|
27
|
-
courses[course].runners.push(runner.id);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
return Object.keys(courses).map(key => courses[key]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function defineCourse(runner) {
|
|
34
|
-
return 'St,' + runner.splits.map(split => split.code).join(',');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function defineLegs(runners) {
|
|
38
|
-
let result = { };
|
|
39
|
-
|
|
40
|
-
runners.filter(runner => runner.splits.length > 0).forEach(runner => {
|
|
41
|
-
runner.splits.forEach((split, idx) => {
|
|
42
|
-
if (validTime(split.split)) {
|
|
43
|
-
let from = idx === 0 ? 'St' : runner.splits[idx - 1].code;
|
|
44
|
-
let code = from + '-' + split.code;
|
|
45
|
-
addRunnerToLeg(result, code, runner, split.split);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function addRunnerToLeg(legs, code, runner, split) {
|
|
54
|
-
let leg = legs[code];
|
|
55
|
-
if (!leg) {
|
|
56
|
-
leg = legs[code] = createLeg(code);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
leg.runners.push({
|
|
60
|
-
id: runner.id,
|
|
61
|
-
fullName: runner.fullName,
|
|
62
|
-
split: split
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function createLeg(code) {
|
|
67
|
-
return {
|
|
68
|
-
code: code,
|
|
69
|
-
runners: []
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function defineRunners(runners) {
|
|
74
|
-
return runners.map(function(runner) {
|
|
75
|
-
return {
|
|
76
|
-
id: runner.id,
|
|
77
|
-
fullName: runner.fullName,
|
|
78
|
-
time: runner.time,
|
|
79
|
-
yearOfBirth: runner.yearOfBirth,
|
|
80
|
-
city: runner.city,
|
|
81
|
-
club: runner.club,
|
|
82
|
-
splits: runner.splits.map(function(split, idx) {
|
|
83
|
-
var splitTime = null;
|
|
84
|
-
if (split.time === '-') {
|
|
85
|
-
splitTime = '-';
|
|
86
|
-
} else if (idx === 0) {
|
|
87
|
-
splitTime = parseTime(split.time);
|
|
88
|
-
} else {
|
|
89
|
-
if (parseTime(split.time) === null || parseTime(runner.splits[idx - 1].time) === null) {
|
|
90
|
-
splitTime = '-';
|
|
91
|
-
} else {
|
|
92
|
-
splitTime = parseTime(split.time) - parseTime(runner.splits[idx - 1].time);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
code: split.code,
|
|
98
|
-
time: split.time,
|
|
99
|
-
split: splitTime
|
|
100
|
-
};
|
|
101
|
-
})
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Define properties of each leg. After this method the leg structure will be enhanced as follows:
|
|
108
|
-
*
|
|
109
|
-
* - runners are sorted per split time per leg
|
|
110
|
-
* - each leg has a property 'idealSplit' (ideal split time of this leg)
|
|
111
|
-
* - each leg has a property 'fastestSplit'
|
|
112
|
-
* - each runner entry of a leg is enhanced with 'splitBehind' and 'splitRank'
|
|
113
|
-
*
|
|
114
|
-
* @param {*} legs leg data structre (only split is relevant)
|
|
115
|
-
*/
|
|
116
|
-
function defineLegProperties(legs) {
|
|
117
|
-
Object.keys(legs).forEach(code => {
|
|
118
|
-
let leg = legs[code];
|
|
119
|
-
leg.runners.sort(function(r1, r2) {
|
|
120
|
-
return r1.split - r2.split;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// calculate the ideal time: take up to 5 fastest on that leg
|
|
124
|
-
let selected = leg.runners.slice(0, Math.min(leg.runners.length, 5)).map(runner => runner.split);
|
|
125
|
-
|
|
126
|
-
// only if there are valid splits for this leg
|
|
127
|
-
if (selected.length > 0) {
|
|
128
|
-
leg.idealSplit = Math.round(selected.reduce(sum) / selected.length);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// only if there are valid splits for this leg
|
|
132
|
-
if (leg.runners.length > 0) {
|
|
133
|
-
leg.fastestSplit = leg.runners[0].split;
|
|
134
|
-
leg.runners[0].splitBehind = '0:00';
|
|
135
|
-
leg.runners.slice(1).forEach(runner => {
|
|
136
|
-
runner.splitBehind = runner.split - leg.fastestSplit;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
leg.runners[0].splitRank = 1;
|
|
140
|
-
leg.runners.forEach((runner, idx, arr) => {
|
|
141
|
-
if (idx > 0) {
|
|
142
|
-
if (runner.split === arr[idx - 1].split) {
|
|
143
|
-
runner.splitRank = arr[idx - 1].splitRank;
|
|
144
|
-
} else {
|
|
145
|
-
runner.splitRank = idx + 1;
|
|
146
|
-
}
|
|
147
|
-
runner.performanceIndex = Math.round(1.0 * leg.idealSplit / runner.split * 100);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculates the weight of each leg regarding the passed in idealTime.
|
|
156
|
-
*
|
|
157
|
-
* @param {*} legs
|
|
158
|
-
* @param {*} idealTime
|
|
159
|
-
*/
|
|
160
|
-
function defineLegWeight(legs, idealTime) {
|
|
161
|
-
Object.keys(legs).forEach((code) => {
|
|
162
|
-
let leg = legs[code];
|
|
163
|
-
leg.weight = leg.idealSplit / idealTime;
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function legCode(splits, idx) {
|
|
168
|
-
if (idx === 0) {
|
|
169
|
-
return 'St-' + splits[0].code;
|
|
170
|
-
} else {
|
|
171
|
-
return splits[idx - 1].code + '-' + splits[idx].code;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Assigns the following property to every valid leg of a runner.
|
|
177
|
-
*
|
|
178
|
-
* - splitBehind: how much time behind the faster runner
|
|
179
|
-
* - splitRank: rank on corresponding leg
|
|
180
|
-
* - leg: code of leg
|
|
181
|
-
*
|
|
182
|
-
* @param {*} runners
|
|
183
|
-
* @param {*} legs
|
|
184
|
-
*/
|
|
185
|
-
function assignLegInfoToSplits(runners, legs) {
|
|
186
|
-
runners.forEach(runner => {
|
|
187
|
-
runner.splits.filter(s => validTime(s.split)).forEach((split, idx) => {
|
|
188
|
-
let code = legCode(runner.splits, idx);
|
|
189
|
-
let leg = legs[code];
|
|
190
|
-
|
|
191
|
-
if (!leg) {
|
|
192
|
-
throw 'leg with code ' + code + ' not defined!';
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
split.leg = leg.code;
|
|
196
|
-
|
|
197
|
-
let legRunner = leg.runners.find(r => r.id === runner.id);
|
|
198
|
-
|
|
199
|
-
split.idealBehind = legRunner ? legRunner.split - leg.idealSplit : '-';
|
|
200
|
-
split.supermanBehind = legRunner ? legRunner.split - leg.fastestSplit : '-';
|
|
201
|
-
split.splitBehind = legRunner ? legRunner.splitBehind : '-';
|
|
202
|
-
split.splitRank = legRunner ? legRunner.splitRank : null;
|
|
203
|
-
split.performanceIndex = legRunner ? legRunner.performanceIndex : null;
|
|
204
|
-
split.weight = leg.weight;
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function rank(runners) {
|
|
210
|
-
runners.forEach((runner, idx) => {
|
|
211
|
-
if (idx === 0) {
|
|
212
|
-
runner.rank = 1;
|
|
213
|
-
} else {
|
|
214
|
-
let prev = runners[idx - 1];
|
|
215
|
-
if (prev.time === runner.time) {
|
|
216
|
-
runner.rank = prev.rank;
|
|
217
|
-
} else if (parseTime(runner.time)) {
|
|
218
|
-
runner.rank = idx + 1;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
class RankingBuilder {
|
|
225
|
-
|
|
226
|
-
constructor(runners) {
|
|
227
|
-
runners.forEach((runner, idx) => {
|
|
228
|
-
for (let i = 0; i < runner.splits.length; i++) {
|
|
229
|
-
let split = runner.splits[i];
|
|
230
|
-
if (i === 0 && validTime(split.time)) {
|
|
231
|
-
split.split = parseTime(split.time);
|
|
232
|
-
} else if (i > 0) {
|
|
233
|
-
let previousSplit = runner.splits[i - 1];
|
|
234
|
-
if (validTime(split.time) && validTime(previousSplit.time)) {
|
|
235
|
-
split.split = parseTime(split.time) - parseTime(previousSplit.time);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
this.courses = defineCourses(runners);
|
|
242
|
-
this.legs = defineLegs(runners);
|
|
243
|
-
this.runners = defineRunners(runners);
|
|
244
|
-
|
|
245
|
-
defineLegProperties(this.legs);
|
|
246
|
-
|
|
247
|
-
// calculate the ideal time [s]
|
|
248
|
-
this.idealTime = Object.keys(this.legs).map(code => this.legs[code].idealSplit).reduce(sum);
|
|
249
|
-
|
|
250
|
-
// each leg's weight is calculated regarding as a ratio of the ideal split time to the ideal time
|
|
251
|
-
defineLegWeight(this.legs, this.idealTime);
|
|
252
|
-
|
|
253
|
-
// now assing the leg information (such as idealTime, weight, ...) to the individual splits of the runners
|
|
254
|
-
assignLegInfoToSplits(this.runners, this.legs);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.runners.forEach(runner => {
|
|
258
|
-
let behind = 0;
|
|
259
|
-
let supermanBehind = 0;
|
|
260
|
-
let weightSum = 0;
|
|
261
|
-
runner.splits.forEach(split => {
|
|
262
|
-
behind += split.idealBehind;
|
|
263
|
-
supermanBehind += split.supermanBehind;
|
|
264
|
-
split.overallIdealBehind = behind;
|
|
265
|
-
split.overallSupermanBehind = supermanBehind;
|
|
266
|
-
split.position = weightSum;
|
|
267
|
-
|
|
268
|
-
weightSum += split.weight;
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// function of function calculating the time at an arbitrary position for a given runner
|
|
273
|
-
let timeFn = (runner) => (pos) => {
|
|
274
|
-
if (pos === 0) {
|
|
275
|
-
return 0;
|
|
276
|
-
} else if (pos >= 1) {
|
|
277
|
-
return parseTime(runner.time);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
let idx = 0;
|
|
281
|
-
let weightSum = 0;
|
|
282
|
-
for (idx = 0; idx < runner.splits.length; idx++) {
|
|
283
|
-
let split = runner.splits[idx];
|
|
284
|
-
if (weightSum + split.weight > pos) {
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
weightSum += split.weight;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let prev = runner.splits[idx - 1];
|
|
291
|
-
let next = runner.splits[idx];
|
|
292
|
-
|
|
293
|
-
let prevTime = parseTime(prev.time);
|
|
294
|
-
let nextTime = parseTime(next.time);
|
|
295
|
-
return prevTime + (pos - prev.position) / (next.position - prev.position) * (nextTime - prevTime);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
let timesAtPosition = (pos) => {
|
|
299
|
-
return this.runners.map(runner => { return { id: runner.id, time: timeFn(runner)(pos) }; });
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
this.runners.forEach(runner => {
|
|
303
|
-
runner.splits.forEach(split => {
|
|
304
|
-
let times = timesAtPosition(split.position + split.weight).filter(entry => entry.time !== null && entry.time > 0);
|
|
305
|
-
times.sort((t1, t2) => t1.time - t2.time);
|
|
306
|
-
|
|
307
|
-
let rank = 1;
|
|
308
|
-
let fastestTime = times[0].time;
|
|
309
|
-
|
|
310
|
-
let lastTime = parseTime(times[0].time);
|
|
311
|
-
for (let idx = 0; idx < times.length; idx++) {
|
|
312
|
-
let entry = times[idx];
|
|
313
|
-
|
|
314
|
-
if (lastTime < parseTime(entry.time)) {
|
|
315
|
-
rank = idx;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (runner.id === entry.id) {
|
|
319
|
-
split.overallRank = rank;
|
|
320
|
-
split.overallBehind = formatTime(entry.time - fastestTime);
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
console.log(this.runners[1]);
|
|
328
|
-
|
|
329
|
-
// calculate the overall rank
|
|
330
|
-
rank(this.runners);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
getCourses() {
|
|
334
|
-
return this.courses;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
getLeg(code) {
|
|
338
|
-
return this.legs[code];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
getLegs() {
|
|
342
|
-
return Object.keys(this.legs).map(code => this.legs[code]);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
getRanking() {
|
|
346
|
-
let runners = this.runners.map(runner => { return { ...runner } });
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
/*
|
|
350
|
-
|
|
351
|
-
for each runner we need the following function
|
|
352
|
-
|
|
353
|
-
f(pos) => timeBehindIdeal
|
|
354
|
-
|
|
355
|
-
using this function it will be possible to calculate the following properties
|
|
356
|
-
|
|
357
|
-
f(runner, split) => {
|
|
358
|
-
overallRank // the rank the current position
|
|
359
|
-
idealBehind // time behind ideal
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
the first function
|
|
363
|
-
|
|
364
|
-
*/
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
module.exports.RankingBuilder = RankingBuilder;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
/*
|
|
372
|
-
/* depends on ordered courses!
|
|
373
|
-
Object.keys(results.legs).forEach((code, idx) => {
|
|
374
|
-
let leg = result.legs[code];
|
|
375
|
-
if (idx === 0) {
|
|
376
|
-
leg.fastestTime = formatTime(leg.fastestSplit);
|
|
377
|
-
leg.idealTime = formatTime(leg.idealSplit);
|
|
378
|
-
} else {
|
|
379
|
-
leg.fastestTime = formatTime(parseTime(result.legs[idx - 1].fastestTime) + leg.fastestSplit);
|
|
380
|
-
leg.idealTime = formatTime(parseTime(result.legs[idx - 1].idealTime) + leg.idealSplit);
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
result.runners.forEach(function(runner) {
|
|
385
|
-
// calculate overall time behind leader
|
|
386
|
-
runner.splits.forEach(function(split, splitIdx) {
|
|
387
|
-
if (!invalidTime(split.time)) {
|
|
388
|
-
let leader = result.runners.map(function(r) {
|
|
389
|
-
return {
|
|
390
|
-
time: r.splits[splitIdx].time,
|
|
391
|
-
rank: r.splits[splitIdx].overallRank
|
|
392
|
-
};
|
|
393
|
-
}).find(function(split) {
|
|
394
|
-
return split.rank === 1;
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
// no leader for this leg?!
|
|
398
|
-
if (leader) {
|
|
399
|
-
let leaderTime = leader.time;
|
|
400
|
-
if (parseTime(split.time) !== null) {
|
|
401
|
-
split.overallBehind = formatTime(parseTime(split.time) - parseTime(leaderTime));
|
|
402
|
-
split.fastestBehind = formatTime(parseTime(split.time) - parseTime(result.legs[splitIdx].fastestTime));
|
|
403
|
-
split.idealBehind = formatTime(parseTime(split.time) - parseTime(result.legs[splitIdx].idealTime));
|
|
404
|
-
} else {
|
|
405
|
-
split.overallBehind = '-';
|
|
406
|
-
split.fastestBehind = '-';
|
|
407
|
-
split.idealBehind = '-';
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// performance index for split
|
|
414
|
-
runner.splits.filter(split => split.split != '-' && split.split != 's' && split.split != '0:00').forEach(split => {
|
|
415
|
-
let leg = findLeg(legs, split.leg);
|
|
416
|
-
split.perfidx = Math.round(1.0 * leg.idealSplit / parseTime(split.split) * 100);
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
return result;
|
|
421
|
-
}
|
|
422
|
-
*/
|
package/lib/reorganize.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
const { parseTime, formatTime } = require('./time');
|
|
2
|
-
const assert = require('assert');
|
|
3
|
-
|
|
4
|
-
module.exports.reorganize = function(runners, course) {
|
|
5
|
-
return runners.filter(r => parseTime(r.time) !== null).map(r => {
|
|
6
|
-
let runner = { ...r };
|
|
7
|
-
let runnerCourse = 'St,' + runner.splits.map(s => s.code).join(',');
|
|
8
|
-
console.log(runner.fullName, runnerCourse);
|
|
9
|
-
if (runnerCourse === course.join(',')) {
|
|
10
|
-
return runner;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let time = 0;
|
|
14
|
-
let splits = [];
|
|
15
|
-
let lastValidTime = 0;
|
|
16
|
-
let lastValidIdx = -1;
|
|
17
|
-
|
|
18
|
-
for (let idx = 0; idx < course.length - 1; idx++) {
|
|
19
|
-
let from = course[idx];
|
|
20
|
-
let to = course[idx + 1];
|
|
21
|
-
let split = getSplit(runner, from, to);
|
|
22
|
-
if (!split) {
|
|
23
|
-
console.log('missing split for runner ' + runner.fullName + " at index " + idx, from, to);
|
|
24
|
-
}
|
|
25
|
-
console.log('split', from + '-' + to, formatTime(lastValidTime), split.diff);
|
|
26
|
-
|
|
27
|
-
if (!split) {
|
|
28
|
-
console.log('split not found for runner', from, to, runner.fullName);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (split.split !== 's') {
|
|
33
|
-
time += split.diff;
|
|
34
|
-
lastValidTime = time;
|
|
35
|
-
lastValidIdx = idx;
|
|
36
|
-
splits.push({
|
|
37
|
-
...split,
|
|
38
|
-
time: formatTime(time)
|
|
39
|
-
});
|
|
40
|
-
console.log('result', idx, split.code, 'time', formatTime(time));
|
|
41
|
-
} else {
|
|
42
|
-
splits.push({
|
|
43
|
-
...split,
|
|
44
|
-
time: '-'
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
runner.splits = splits;
|
|
49
|
-
return runner;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getSplit(runner, from, to) {
|
|
54
|
-
for (let idx = 0; idx < runner.splits.length; idx++) {
|
|
55
|
-
if (from === 'St') {
|
|
56
|
-
// assumption: all runners have same start
|
|
57
|
-
const split = runner.splits[0];
|
|
58
|
-
return {
|
|
59
|
-
...split,
|
|
60
|
-
diff: parseTime(split.time)
|
|
61
|
-
};
|
|
62
|
-
} else if (to === 'Zi') {
|
|
63
|
-
throw "to Zi not supported";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (let j = 1; j < runner.splits.length; j++) {
|
|
67
|
-
if (from === runner.splits[j - 1].code && to === runner.splits[j].code) {
|
|
68
|
-
const prevSplit = runner.splits[j - 1];
|
|
69
|
-
const split = runner.splits[j];
|
|
70
|
-
return {
|
|
71
|
-
...split,
|
|
72
|
-
diff: parseTime(split.time) - parseTime(prevSplit.time)
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log('busted!', from, to, runner);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function getLegTime(runner, from, to) {
|
|
83
|
-
// from: [41,38]
|
|
84
|
-
// to: [42]
|
|
85
|
-
for (let idx = 0; idx < runner.splits.length; idx++) {
|
|
86
|
-
if (from[0] === 'St') {
|
|
87
|
-
// assumption: all runners have same start
|
|
88
|
-
return parseTime(runner.splits[0].time);
|
|
89
|
-
} else if (to[0] === 'Zi') {
|
|
90
|
-
return parseTime(runner.totalTime) - parseTime(runner.splits[runner.splits.length - 1].time);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
for (let j = 1; j < runner.splits.length; j++) {
|
|
94
|
-
if (from.includes(runner.splits[j - 1].code) && to.includes(runner.splits[j].code)) {
|
|
95
|
-
let currentTime = parseTime(runner.splits[j].time);
|
|
96
|
-
let previousTime = parseTime(runner.splits[j - 1].time);
|
|
97
|
-
if (currentTime === null) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// find last valid previous time or 0
|
|
102
|
-
let k = j - 2;
|
|
103
|
-
while (previousTime === null) {
|
|
104
|
-
if (k === -1) {
|
|
105
|
-
previousTime = 0;
|
|
106
|
-
} else {
|
|
107
|
-
previousTime = parseTime(runner.splits[k].time);
|
|
108
|
-
k--;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let legTime = currentTime - previousTime;
|
|
113
|
-
if (legTime === 0) {
|
|
114
|
-
console.log('*', runner.splits[j], currentTime, runner.splits[j - 1], previousTime);
|
|
115
|
-
} else if (legTime < 0) {
|
|
116
|
-
console.log('*', runner.splits[j], currentTime, runner.splits[j - 1], previousTime);
|
|
117
|
-
}
|
|
118
|
-
return legTime;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
console.log('busted!', from, to);
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Reorganize a single runner to the given course. The course is specified as an array
|
|
129
|
-
* of arrays. The elements in the inner array represent the controls that shall be
|
|
130
|
-
* combined.
|
|
131
|
-
*
|
|
132
|
-
* @param {*} runner
|
|
133
|
-
* @param {*} course
|
|
134
|
-
*/
|
|
135
|
-
module.exports.reorganizeBbn = function(runner, course) {
|
|
136
|
-
assert(runner.splits.length === course.length, 'number of splits must match', runner.splits.length, course.length);
|
|
137
|
-
|
|
138
|
-
// course = [[41,38],[42],[43], ...]
|
|
139
|
-
let time = 0;
|
|
140
|
-
let splits = [];
|
|
141
|
-
for (let idx = 0; idx < course.length; idx++) {
|
|
142
|
-
let from = idx === 0 ? ['St'] : course[idx - 1];
|
|
143
|
-
let to = course[idx];
|
|
144
|
-
let legTime = getLegTime(runner, from, to);
|
|
145
|
-
if (legTime !== null) {
|
|
146
|
-
time += legTime;
|
|
147
|
-
}
|
|
148
|
-
splits.push({ code: to.join('|'), time: legTime === null ? '-' : formatTime(time) });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
assert(runner.splits.length === splits.length, 'number of splits must not change', runner.splits.length, splits.length, course.length);
|
|
152
|
-
|
|
153
|
-
runner.splits = splits;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
module.exports.defineSubRanking = function(input, from, to) {
|
|
158
|
-
let runners;
|
|
159
|
-
if (typeof input.categories !== 'undefined') {
|
|
160
|
-
runners = input.categories.flatMap(function(category) { return category.runners; })
|
|
161
|
-
} else {
|
|
162
|
-
runners = input.runners;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return runners.map(function(runner) {
|
|
166
|
-
var fromTime, toTime;
|
|
167
|
-
const course = [];
|
|
168
|
-
|
|
169
|
-
if (from === 'St') {
|
|
170
|
-
fromTime = 0;
|
|
171
|
-
course.push(from);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (to === 'Zi') {
|
|
175
|
-
toTime = parseTime(runner.time);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for (var i = 0; i < runner.splits.length; i++) {
|
|
179
|
-
var split = runner.splits[i];
|
|
180
|
-
if (parseTime(split.time) === null) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (split.code === from && typeof fromTime === 'undefined') {
|
|
185
|
-
fromTime = parseTime(split.time);
|
|
186
|
-
course.push(split.code);
|
|
187
|
-
|
|
188
|
-
} else if (split.code === to) {
|
|
189
|
-
toTime = parseTime(split.time);
|
|
190
|
-
course.push(split.code);
|
|
191
|
-
break;
|
|
192
|
-
|
|
193
|
-
} else if (typeof fromTime !== 'undefined') {
|
|
194
|
-
course.push(split.code);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (typeof fromTime !== 'undefined' && typeof toTime !== 'undefined' && toTime !== null) {
|
|
199
|
-
const time = formatTime(toTime - fromTime);
|
|
200
|
-
return {
|
|
201
|
-
fullName: runner.fullName,
|
|
202
|
-
time: time,
|
|
203
|
-
course: course.join('-')
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
fullName: runner.fullName
|
|
209
|
-
};
|
|
210
|
-
}).filter(runner => typeof runner.time !== 'undefined')
|
|
211
|
-
.sort((e1, e2) => parseTime(e1.time) - parseTime(e2.time));
|
|
212
|
-
}
|