diff --git a/apps/fxc-front/src/app/components/2d/path-element.ts b/apps/fxc-front/src/app/components/2d/path-element.ts
index 6317ca23..3b9cf9f2 100644
--- a/apps/fxc-front/src/app/components/2d/path-element.ts
+++ b/apps/fxc-front/src/app/components/2d/path-element.ts
@@ -13,12 +13,12 @@ import { FaiSectors } from '../../gm/fai-sectors';
import { addAltitude } from '../../logic/elevation';
import { getCurrentUrl, pushCurrentState } from '../../logic/history';
import { drawRoute } from '../../logic/messages';
-import { CircuitType, Score } from '../../logic/score/scorer';
+import { CircuitType, getCircuitType, Score } from '../../logic/score/scorer';
import { setDistance, setEnabled, setRoute, setScore } from '../../redux/planner-slice';
import { RootState, store } from '../../redux/store';
import { PlannerElement } from './planner-element';
-import { OptimizationResult, optimize, ScoringRules, ScoringTrack } from 'optimizer';
-import { LeagueCode } from '../../logic/score/league/leagues';
+import { getOptimizer, ScoringTrack } from 'optimizer';
+import { getScoringRules } from '../../logic/score/league/leagues';
// Route color by circuit type.
const ROUTE_STROKE_COLORS = {
@@ -225,10 +225,10 @@ export class PathElement extends connect(store)(LitElement) {
this.closingSector.addListener('rightclick', (e) => this.appendToPath(e.latLng));
}
- if (score.closingRadius) {
+ if (score.closingRadiusM) {
const center = points[score.indexes[0]];
this.closingSector.center = center;
- this.closingSector.radius = score.closingRadius;
+ this.closingSector.radius = score.closingRadiusM;
this.closingSector.update();
this.closingSector.setMap(this.map);
} else {
@@ -252,67 +252,26 @@ export class PathElement extends connect(store)(LitElement) {
private computeScore(points: LatLon[]): Score {
const track: ScoringTrack = {
- points: points.map((point, i) => {
- return {
- ...point,
- alt: 0,
- timeSec: i * 60,
- };
- }),
- minTimeSec: new Date().getTime() / 1000,
+ points: points.map((point, i) => ({ ...point, alt: 0, timeSec: i * 60 })),
+ startTimeSec: new Date().getTime() / 1000,
};
- const result = optimize({ track }, this.getLeague()).next().value;
- const score = new Score({
- circuit: this.getCircuitType(result),
- distance: result.lengthKm * 1000,
+ const result = getOptimizer({ track }, getScoringRules(this.league)).next().value;
+ return new Score({
+ circuit: getCircuitType(result.circuit),
+ distanceM: result.lengthKm * 1000,
multiplier: result.multiplier,
- closingRadius: result.closingRadius ? result.closingRadius * 1000 : null,
+ closingRadiusM: result.closingRadius ? result.closingRadius * 1000 : null,
indexes: result.solutionIndices,
+ points: result.score,
});
- // force the score as computed because of an unwanted side effect in constructor.
- score.forcePoints(result.score);
- return score;
- }
-
- private getLeague(): ScoringRules {
- switch (this.league as LeagueCode) {
- case 'czl':
- return ScoringRules.CzechLocal;
- case 'cze':
- return ScoringRules.CzechEuropean;
- case 'czo':
- return ScoringRules.CzechOutsideEurope;
- case 'fr':
- return ScoringRules.FederationFrancaiseVolLibre;
- case 'leo':
- return ScoringRules.Leonardo;
- case 'nor':
- return ScoringRules.Norway;
- case 'ukc':
- return ScoringRules.UnitedKingdomClub;
- case 'uki':
- return ScoringRules.UnitedKingdomInternational;
- case 'ukn':
- return ScoringRules.UnitedKingdomNational;
- case 'xc':
- return ScoringRules.XContest;
- case 'xcppg':
- return ScoringRules.XContestPPG;
- case 'wxc':
- return ScoringRules.WorldXC;
- }
- }
-
- private getCircuitType(result: OptimizationResult) {
- return result.circuit as unknown as CircuitType;
}
// Sends a message to the iframe host with the changes.
private postScoreToHost(score: Score) {
let kms = '';
let circuit = '';
- if (score.distance && window.parent) {
- kms = (score.distance / 1000).toFixed(1);
+ if (score.distanceM && window.parent) {
+ kms = (score.distanceM / 1000).toFixed(1);
circuit = CIRCUIT_SHORT_NAME[score.circuit];
if (score.circuit == CircuitType.OpenDistance) {
circuit += score.indexes.length - 2;
diff --git a/apps/fxc-front/src/app/components/2d/planner-element.ts b/apps/fxc-front/src/app/components/2d/planner-element.ts
index 270c5a8e..65ca3958 100644
--- a/apps/fxc-front/src/app/components/2d/planner-element.ts
+++ b/apps/fxc-front/src/app/components/2d/planner-element.ts
@@ -139,7 +139,7 @@ export class PlannerElement extends connect(store)(LitElement) {
${this.score.circuit}
- ${unsafeHTML(units.formatUnit(this.score.distance / 1000, this.units.distance, undefined, 'unit'))}
+ ${unsafeHTML(units.formatUnit(this.score.distanceM / 1000, this.units.distance, undefined, 'unit'))}
diff --git a/apps/fxc-front/src/app/logic/score/league/leagues.ts b/apps/fxc-front/src/app/logic/score/league/leagues.ts
index 6eb26830..4a63865c 100644
--- a/apps/fxc-front/src/app/logic/score/league/leagues.ts
+++ b/apps/fxc-front/src/app/logic/score/league/leagues.ts
@@ -1,4 +1,10 @@
-export const LEAGUES: { [name: string]: string } = {
+import { ScoringRules } from 'optimizer';
+
+// allowed league codes
+export const leagueCodes = ['czl', 'cze', 'czo', 'fr', 'leo', 'nor', 'ukc', 'uki', 'ukn', 'xc', 'xcppg', 'wxc'];
+export type LeagueCode = (typeof leagueCodes)[number];
+
+export const LEAGUES: Readonly> = {
czl: 'Czech (ČPP local)',
cze: 'Czech (ČPP Europe)',
czo: 'Czech (ČPP outside Europe)',
@@ -13,7 +19,32 @@ export const LEAGUES: { [name: string]: string } = {
wxc: 'World XC Online Contest',
};
-// allowed league codes
-// ensure that all league codes defined in each League sub classes are in this
-// closed set.
-export type LeagueCode = 'czl' | 'cze' | 'czo' | 'fr' | 'leo' | 'nor' | 'ukc' | 'uki' | 'ukn' | 'xc' | 'xcppg' | 'wxc';
+export function getScoringRules(league: string): ScoringRules {
+ switch (league) {
+ case 'czl':
+ return ScoringRules.CzechLocal;
+ case 'cze':
+ return ScoringRules.CzechEuropean;
+ case 'czo':
+ return ScoringRules.CzechOutsideEurope;
+ case 'fr':
+ return ScoringRules.FederationFrancaiseVolLibre;
+ case 'leo':
+ return ScoringRules.Leonardo;
+ case 'nor':
+ return ScoringRules.Norway;
+ case 'ukc':
+ return ScoringRules.UnitedKingdomClub;
+ case 'uki':
+ return ScoringRules.UnitedKingdomInternational;
+ case 'ukn':
+ return ScoringRules.UnitedKingdomNational;
+ case 'xc':
+ return ScoringRules.XContest;
+ case 'xcppg':
+ return ScoringRules.XContestPPG;
+ case 'wxc':
+ return ScoringRules.WorldXC;
+ }
+ throw Error('no corresponding rule for ' + league);
+}
diff --git a/apps/fxc-front/src/app/logic/score/scorer.ts b/apps/fxc-front/src/app/logic/score/scorer.ts
index a5ea9211..a847db85 100644
--- a/apps/fxc-front/src/app/logic/score/scorer.ts
+++ b/apps/fxc-front/src/app/logic/score/scorer.ts
@@ -1,3 +1,5 @@
+import { CircuitType as OptimizerCircuitType } from 'optimizer';
+
export enum CircuitType {
OpenDistance = 'Open distance',
FlatTriangle = 'Flat triangle',
@@ -5,23 +7,25 @@ export enum CircuitType {
OutAndReturn = 'Out and return',
}
+export function getCircuitType(circuit?: OptimizerCircuitType) {
+ return circuit as unknown as CircuitType;
+}
+
+
export class Score {
- distance: number;
+ distanceM: number;
indexes: number[];
multiplier: number;
circuit: CircuitType;
- closingRadius: number | null;
+ closingRadiusM: number | null;
points: number;
- constructor(score: Partial>) {
- this.distance = score.distance || 0;
- this.indexes = score.indexes || [];
- this.multiplier = score.multiplier || 1;
- this.circuit = score.circuit || CircuitType.OpenDistance;
- this.closingRadius = score.closingRadius || null;
- this.points = (this.distance * this.multiplier) / 1000;
- }
- public forcePoints(points: number){
- this.points = points;
+ constructor(score: Partial) {
+ this.distanceM = score.distanceM ?? 0;
+ this.indexes = score.indexes ?? [];
+ this.multiplier = score.multiplier ?? 1;
+ this.circuit = score.circuit ?? CircuitType.OpenDistance;
+ this.closingRadiusM = score.closingRadiusM ?? null;
+ this.points = score.points ?? 0;
}
}
diff --git a/apps/fxc-front/vite.config.ts b/apps/fxc-front/vite.config.ts
index c6c47f6b..02b944e2 100644
--- a/apps/fxc-front/vite.config.ts
+++ b/apps/fxc-front/vite.config.ts
@@ -93,7 +93,7 @@ export default defineConfig({
define: {
__BUILD_TIMESTAMP__: JSON.stringify(format(new Date(), 'yyyyMMdd.HHmm')),
__AIRSPACE_DATE__: JSON.stringify(getAirspaceDate()),
- global: {}, // required by igc-xc-score
+ global: {}, // required by igc-xc-score. TODO(vicb): check how to remove this
},
});
diff --git a/libs/optimizer/src/index.ts b/libs/optimizer/src/index.ts
index bf970e16..9affcc26 100644
--- a/libs/optimizer/src/index.ts
+++ b/libs/optimizer/src/index.ts
@@ -1,7 +1,7 @@
-export { optimize } from './lib/optimizer';
+export { getOptimizer } from './lib/optimizer';
export type {
LatLonAltTime,
- OptimizedCircuitType,
+ CircuitType,
ScoringTrack,
OptimizationResult,
OptimizationOptions,
diff --git a/libs/optimizer/src/lib/fixtures/optimizer.fixtures.ts b/libs/optimizer/src/lib/fixtures/optimizer.fixtures.ts
index cb14c33b..4637bad8 100644
--- a/libs/optimizer/src/lib/fixtures/optimizer.fixtures.ts
+++ b/libs/optimizer/src/lib/fixtures/optimizer.fixtures.ts
@@ -1,4 +1,4 @@
-import { OptimizationRequest, OptimizationResult, OptimizedCircuitType } from '../optimizer';
+import { OptimizationRequest, OptimizationResult, CircuitType } from '../optimizer';
import { computeDestinationPoint, getGreatCircleBearing, getPreciseDistance } from 'geolib';
import { createSegments } from '../utils/createSegments';
import { concatTracks } from '../utils/concatTracks';
@@ -25,7 +25,7 @@ export type OptimizerFixture = {
export function createEmptyTrackFixture(): OptimizerFixture {
return {
givenRequest: {
- track: { points: [], minTimeSec: 0 },
+ track: { points: [], startTimeSec: 0 },
},
givenRules: ScoringRules.FederationFrancaiseVolLibre,
expectedResult: {
@@ -73,7 +73,7 @@ export function createFreeDistanceFixture(
score: distance * multiplier,
lengthKm: distance,
multiplier,
- circuit: OptimizedCircuitType.OpenDistance,
+ circuit: CircuitType.OpenDistance,
optimal: true,
},
};
@@ -119,7 +119,7 @@ export function createFreeDistance1PointFixture(
score: distance * multiplier,
lengthKm: distance,
multiplier,
- circuit: OptimizedCircuitType.OpenDistance,
+ circuit: CircuitType.OpenDistance,
optimal: true,
},
};
@@ -177,7 +177,7 @@ export function createFreeDistance2PointsFixture(
score: distance * multiplier,
lengthKm: distance,
multiplier,
- circuit: OptimizedCircuitType.OpenDistance,
+ circuit: CircuitType.OpenDistance,
optimal: true,
},
};
@@ -244,7 +244,7 @@ export function createFreeDistance3PointsFixture(
score: distance * multiplier,
lengthKm: distance,
multiplier,
- circuit: OptimizedCircuitType.OpenDistance,
+ circuit: CircuitType.OpenDistance,
optimal: true,
},
};
@@ -270,7 +270,7 @@ export function createClosedFlatTriangleFixture(
throw new Error('invalid test data: not a flat triangle');
}
const multiplier = getFlatTriangleMultiplier(givenRules);
- return createTriangleFixture(start, p1, p2, nbSegments, givenRules, multiplier, OptimizedCircuitType.FlatTriangle);
+ return createTriangleFixture(start, p1, p2, nbSegments, givenRules, multiplier, CircuitType.FlatTriangle);
}
/**
@@ -298,7 +298,7 @@ export function createClosedFaiTriangleFixture(
throw new Error('invalid test data: not a FAI triangle');
}
const multiplier = getFaiTriangleMultiplier(givenRules);
- return createTriangleFixture(start, p1, p2, nbSegments, givenRules, multiplier, OptimizedCircuitType.FaiTriangle);
+ return createTriangleFixture(start, p1, p2, nbSegments, givenRules, multiplier, CircuitType.FaiTriangle);
}
/**
@@ -430,7 +430,7 @@ function createTriangleFixture(
nbSegments: number,
givenRules: ScoringRules,
multiplier: number,
- circuit: OptimizedCircuitType,
+ circuit: CircuitType,
): OptimizerFixture {
const distance1 = getPreciseDistance(start, p1);
const distance2 = getPreciseDistance(p1, p2);
diff --git a/libs/optimizer/src/lib/optimizer.spec.ts b/libs/optimizer/src/lib/optimizer.spec.ts
index da2b2e3d..2d29c0c0 100644
--- a/libs/optimizer/src/lib/optimizer.spec.ts
+++ b/libs/optimizer/src/lib/optimizer.spec.ts
@@ -1,4 +1,4 @@
-import { OptimizationResult, optimize } from './optimizer';
+import { OptimizationResult, getOptimizer } from './optimizer';
import {
createClosedFaiTriangleFixture,
createClosedFaiTriangleFixtureWithSmallCycle,
@@ -34,11 +34,11 @@ describe('optimizer', () => {
ScoringRules.XContestPPG,
ScoringRules.WorldXC,
].forEach((rules) => {
- describe(ScoringRules[rules] + ' rules', () => {
+ describe(`${ScoringRules[rules]} rules`, () => {
const oneSegmentPerBranch = 1;
const tenSegmentsPerBranch = 10;
[oneSegmentPerBranch, tenSegmentsPerBranch].forEach((nbSegmentsPerBranch) => {
- describe('given a free distance request (' + nbSegmentsPerBranch + ' segments(s)/branch)', () => {
+ describe(`given a free distance request (${nbSegmentsPerBranch} segments(s)/branch)`, () => {
const fixture = createFreeDistanceFixture(
{ lat: 45, lon: 5 },
{ lat: 45, lon: 6 },
@@ -51,7 +51,7 @@ describe('optimizer', () => {
});
describe(
- 'given a free distance with 1 intermediate point request (' + nbSegmentsPerBranch + ' segment(s)/branch)',
+ `given a free distance with 1 intermediate point request (${nbSegmentsPerBranch} segment(s)/branch)`,
() => {
const fixture = createFreeDistance1PointFixture(
{ lat: 45, lon: 5 },
@@ -67,7 +67,7 @@ describe('optimizer', () => {
);
describe(
- 'given a free distance with 2 intermediate points request (' + nbSegmentsPerBranch + ' segment(s)/branch)',
+ `given a free distance with 2 intermediate points request (${nbSegmentsPerBranch} segment(s)/branch)`,
() => {
const fixture = createFreeDistance2PointsFixture(
{ lat: 45, lon: 5 },
@@ -84,7 +84,7 @@ describe('optimizer', () => {
);
describe(
- 'given a free distance with 3 intermediate points request (' + nbSegmentsPerBranch + ' segment(s)/branch)',
+ `given a free distance with 3 intermediate points request (${nbSegmentsPerBranch} segment(s)/branch)`,
() => {
const fixture = createFreeDistance3PointsFixture(
{ lat: 45, lon: 5 },
@@ -101,7 +101,7 @@ describe('optimizer', () => {
},
);
- describe('given a closed flat triangle request (' + nbSegmentsPerBranch + ' segment(s)/branch)', () => {
+ describe(`given a closed flat triangle request (${nbSegmentsPerBranch} segment(s)/branch)`, () => {
const fixture = createClosedFlatTriangleFixture(
{ lat: 45, lon: 5 },
{ lat: 45, lon: 6 },
@@ -114,7 +114,7 @@ describe('optimizer', () => {
});
});
- describe('given a closed FAI triangle request (' + nbSegmentsPerBranch + ' segment(s)/branch)', () => {
+ describe(`given a closed FAI triangle request (${nbSegmentsPerBranch} segment(s)/branch)`, () => {
const fixture = createClosedFaiTriangleFixture(
{ lat: 45, lon: 5 },
{ lat: 45, lon: 6 },
@@ -153,8 +153,10 @@ describe('optimizer', () => {
});
});
+ // TODO: IsAsExpected does not really describe the behavior. Something with expect(optimize(...)).toHaveScore(...);
+ // should be better
function expectOptimizationIsAsExpected(fixture: OptimizerFixture) {
- const optimization = optimize(fixture.givenRequest, fixture.givenRules);
+ const optimization = getOptimizer(fixture.givenRequest, fixture.givenRules);
let currentResult: IteratorResult,
done = false;
while (!done) {
diff --git a/libs/optimizer/src/lib/optimizer.ts b/libs/optimizer/src/lib/optimizer.ts
index e0c31c0d..ec657435 100644
--- a/libs/optimizer/src/lib/optimizer.ts
+++ b/libs/optimizer/src/lib/optimizer.ts
@@ -6,7 +6,8 @@ import { ScoringRules, scoringRules } from './scoringRules';
import { getDistance } from 'geolib';
// When the track has not enough points (<5), we build a new one by adding interpolated points between existing ones.
-// This constant sets the number of segments to build between two existing points.
+// see this issue https://github.com/mmomtchev/igc-xc-score/issues/231 for removing this limitation
+const MIN_POINTS = 5;
const NUM_SEGMENTS_BETWEEN_POINTS = 2;
// For adding interpolated points, this constant adjusts the proximity of the points to the starting point of
@@ -15,71 +16,90 @@ const NUM_SEGMENTS_BETWEEN_POINTS = 2;
const DISTRIBUTION_FACTOR_FOR_ADDED_POINTS = 1e-5;
-/**
- * lat: array of latitudes
- * lon: array of longitudes
- * alt: array of altitudes
- * timeSec: array of time in seconds elapsed since the beginning of the track
- */
export interface LatLonAltTime {
alt: number;
lat: number;
lon: number;
- timeSec: number
+ /**
+ * time in seconds elapsed since the beginning of the track (see ScoringTrack.minTimeSec)
+ */
+ timeSec: number;
}
-/**
- * points: the points that describe the track
- * minTimeSec: beginning of the track (seconds elapsed since 01-01-1970T00:00:00.000)
- */
export interface ScoringTrack {
+ /**
+ * the points that describe the track
+ */
points: LatLonAltTime[];
- minTimeSec: number;
+ /**
+ * the instant when the track starts (seconds elapsed since 01-01-1970T00:00:00.000)
+ * the "timeSec" values in LatLonAltTime's are offsets according to this instant.
+ */
+ startTimeSec: number;
}
-/**
- * maxCycleDurationMs: maximum duration in milliseconds for an optimization round trip. `
- * If undefined, calculation duration is unbounded.
- * maxNumCycles: maximum number of iterations allowed for an optimization round trip.
- * If undefined, number of allowed iterations is unbounded
- */
export interface OptimizationOptions {
+ /**
+ * maximum duration in milliseconds for an optimization round trip.
+ * If undefined, calculation duration is unbounded.
+ */
maxCycleDurationMs?: number;
+ /**
+ * maximum number of iterations allowed for an optimization round trip.
+ * If undefined, number of allowed iterations is unbounded
+ */
maxNumCycles?: number;
}
/**
* optimize function argument
- * track: the ScoringTrack to optimize
- * options: the OptimizationOptions for the computation
*/
export interface OptimizationRequest {
+ /**
+ * the ScoringTrack to optimize
+ */
track: ScoringTrack;
+ /**
+ * the OptimizationOptions for the computation
+ */
options?: OptimizationOptions;
}
-export enum OptimizedCircuitType {
+export enum CircuitType {
OpenDistance = 'Open distance',
FlatTriangle = 'Flat triangle',
FaiTriangle = 'Fai triangle',
OutAndReturn = 'Out and return',
}
-/**
- * score: the score for the track in the given league
- * lengthKm: the length of the optimized track in kms
- * multiplier: multiplier for computing score. score = lengthKm * multiplier
- * circuit: type of the optimized track
- * closingRadius: if applicable, distance in m for closing the circuit
- * optimal: the result is optimal (no need to get a next result of Iterator)
- */
export interface OptimizationResult {
+ /**
+ * the score for the track in the given league
+ */
score: number;
+ /**
+ * the length of the optimized track in kms
+ */
lengthKm: number;
+ /**
+ * multiplier for computing score. score = lengthKm * multiplier
+ */
multiplier: number;
- circuit?: OptimizedCircuitType;
+ /**
+ * type of the optimized track
+ */
+ circuit?: CircuitType;
+ /**
+ * if applicable, distance in m for closing the circuit
+ */
closingRadius?: number;
- solutionIndices: number[],
+ /**
+ * indices of solutions points in
+ */
+ solutionIndices: number[];
+ /**
+ * the result is optimal (no need to get a next result of Iterator)
+ */
optimal: boolean;
}
@@ -87,22 +107,23 @@ const ZERO_SCORE: OptimizationResult = {
score: 0,
lengthKm: 0,
multiplier: 0,
- circuit: undefined,
- closingRadius: undefined,
solutionIndices: [],
optimal: true,
};
-const MIN_POINTS = 5;
-
/**
- * computes the score for the flight
- * @param request the OptimizationRequest
- * @param league the LeagueCode of the league rules to follow
+ * returns an iterative optimizer that computes iteratively the score for the flight. At each iteration, the score
+ * should be a better solutions.
+ * @param request the OptimizationRequest. if request.options is undefined, then there will be one iteration, and the result
+ * will be the best solution
+ * @param scoringRules the ScoringRules to apply for computation
* @return an Iterator over the successive OptimizationResult
* @see README.md
*/
-export function* optimize(request: OptimizationRequest, league: ScoringRules): Iterator {
+export function* getOptimizer(
+ request: OptimizationRequest,
+ scoringRules: ScoringRules,
+): Iterator {
if (request.track.points.length == 0) {
console.warn('Empty track received in optimization request. Returns a 0 score');
return ZERO_SCORE;
@@ -110,13 +131,13 @@ export function* optimize(request: OptimizationRequest, league: ScoringRules): I
const originalTrack = request.track;
const solverTrack = buildValidTrackForSolver(originalTrack);
const flight = toIgcFile(solverTrack);
- const scoringRules = toScoringRules(league);
+ const solverScoringRules = toScoringRules(scoringRules);
const options = toOptions(request.options);
- const solutionIterator = solver(flight, scoringRules || {}, options);
+ const solutionIterator = solver(flight, solverScoringRules || {}, options);
while (true) {
const solution = solutionIterator.next();
if (solution.done) {
- console.debug("solution", JSON.stringify(solution.value, undefined, 2));
+ console.debug('solution', JSON.stringify(solution.value, undefined, 2));
return toResult(solution.value, originalTrack, solverTrack);
}
yield toResult(solution.value, originalTrack, solverTrack);
@@ -131,8 +152,9 @@ function buildValidTrackForSolver(track: ScoringTrack) {
if (track.points.length >= MIN_POINTS) {
return track;
}
- console.debug('not enough points (%s) in track. Interpolate intermediate points', track.points.length);
- let newTrack: ScoringTrack = track;
+ console.debug(`not enough points (${track.points.length}) in track. Interpolate intermediate points`);
+ // avoid potential future bugs with a defensive copy to avoid unwanted mutations on inputs.
+ let newTrack: ScoringTrack = deepCopy(track);
while (newTrack.points.length < MIN_POINTS) {
const segments: ScoringTrack[] = [];
for (let i = 1; i < newTrack.points.length; i++) {
@@ -141,7 +163,7 @@ function buildValidTrackForSolver(track: ScoringTrack) {
createSegments(
newTrack.points[i - 1],
newTrack.points[i],
- track.minTimeSec,
+ track.startTimeSec,
NUM_SEGMENTS_BETWEEN_POINTS,
DISTRIBUTION_FACTOR_FOR_ADDED_POINTS,
),
@@ -149,7 +171,7 @@ function buildValidTrackForSolver(track: ScoringTrack) {
}
newTrack = concatTracks(...segments);
}
- console.debug('new track has', newTrack.points.length, 'points');
+ console.debug(`new track has ${newTrack.points.length} points`);
return newTrack;
}
@@ -159,7 +181,7 @@ function buildValidTrackForSolver(track: ScoringTrack) {
* @param track the source track
*/
function toIgcFile(track: ScoringTrack): IGCFile {
- const fixes = track.points.map(point => {
+ const fixes = track.points.map((point): BRecord => {
const timeMilliseconds = point.timeSec * 1000;
return {
timestamp: timeMilliseconds,
@@ -172,12 +194,12 @@ function toIgcFile(track: ScoringTrack): IGCFile {
extensions: {},
fixAccuracy: null,
enl: null,
- } as BRecord;
+ };
});
// we ignore some properties of the igc-file, as they are not required for the computation
// @ts-ignore
return {
- date: new Date(track.minTimeSec * 1000).toISOString(),
+ date: new Date(track.startTimeSec * 1000).toISOString(),
fixes: fixes,
};
}
@@ -197,12 +219,12 @@ function toOptions(options?: OptimizationOptions): SolverOptions {
function toResult(solution: Solution, originalTrack: ScoringTrack, solverTrack: ScoringTrack): OptimizationResult {
return {
- score: (solution.score || 0),
- lengthKm: (solution.scoreInfo?.distance || 0),
+ score: solution.score ?? 0,
+ lengthKm: solution.scoreInfo?.distance ?? 0,
multiplier: solution.opt.scoring.multiplier,
- circuit: toCircuitType(solution.opt.scoring.code as CircuitTypeCode),
+ circuit: toCircuitType(solution.opt.scoring.code),
closingRadius: getClosingRadius(solution),
- solutionIndices: getIndices(solution,originalTrack,solverTrack),
+ solutionIndices: getIndices(solution, originalTrack, solverTrack),
optimal: solution.optimal || false,
};
}
@@ -229,16 +251,16 @@ function getClosingRadius(solution: Solution) {
type CircuitTypeCode = 'od' | 'tri' | 'fai' | 'oar';
-function toCircuitType(code: CircuitTypeCode) {
- switch (code) {
+function toCircuitType(code: string) {
+ switch (code as CircuitTypeCode) {
case 'od':
- return OptimizedCircuitType.OpenDistance;
+ return CircuitType.OpenDistance;
case 'fai':
- return OptimizedCircuitType.FaiTriangle;
+ return CircuitType.FaiTriangle;
case 'oar':
- return OptimizedCircuitType.OutAndReturn;
+ return CircuitType.OutAndReturn;
case 'tri':
- return OptimizedCircuitType.FlatTriangle;
+ return CircuitType.FlatTriangle;
}
}
@@ -318,3 +340,9 @@ function getIndexInOriginalTrack(
}
return indexInOriginalTrack;
}
+
+// another implementation of deep copy to avoid dependency from @flyxc/common
+// Not the most performant solution but is used only for slow dimension problems
+function deepCopy (source: T): T {
+ return JSON.parse(JSON.stringify(source));
+}
diff --git a/libs/optimizer/src/lib/utils/concatTracks.ts b/libs/optimizer/src/lib/utils/concatTracks.ts
index 33b94060..5d76e373 100644
--- a/libs/optimizer/src/lib/utils/concatTracks.ts
+++ b/libs/optimizer/src/lib/utils/concatTracks.ts
@@ -9,7 +9,7 @@ import { ScoringTrack } from '../optimizer';
export function concatTracks(...tracks: ScoringTrack[]): ScoringTrack {
const concatenated: ScoringTrack = {
points: [],
- minTimeSec: 0,
+ startTimeSec: 0,
};
for (const track of tracks) {
diff --git a/libs/optimizer/src/lib/utils/createSegments.ts b/libs/optimizer/src/lib/utils/createSegments.ts
index 5ba9c7dc..646089e9 100644
--- a/libs/optimizer/src/lib/utils/createSegments.ts
+++ b/libs/optimizer/src/lib/utils/createSegments.ts
@@ -20,7 +20,7 @@ export function createSegments(
nbSegments: number,
distributionFactor: number = 1,
): ScoringTrack {
- const result: ScoringTrack = { points: [], minTimeSec };
+ const result: ScoringTrack = { points: [], startTimeSec: minTimeSec };
appendToResult(from);