From 7ac7a1c9aefa8544f6acb2d39dee6b6993331e79 Mon Sep 17 00:00:00 2001 From: Christophe TARET Date: Tue, 28 May 2024 17:59:30 +0200 Subject: [PATCH] polish: take in account code review --- .../src/app/components/2d/path-element.ts | 71 ++------- .../src/app/components/2d/planner-element.ts | 2 +- .../src/app/logic/score/league/leagues.ts | 41 ++++- apps/fxc-front/src/app/logic/score/scorer.ts | 28 ++-- libs/optimizer/src/index.ts | 4 +- .../src/lib/fixtures/optimizer.fixtures.ts | 18 +-- libs/optimizer/src/lib/optimizer.spec.ts | 20 +-- libs/optimizer/src/lib/optimizer.ts | 143 ++++++++++-------- libs/optimizer/src/lib/utils/concatTracks.ts | 2 +- .../optimizer/src/lib/utils/createSegments.ts | 2 +- 10 files changed, 175 insertions(+), 156 deletions(-) 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/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..2e9ae05b 100644 --- a/libs/optimizer/src/lib/optimizer.ts +++ b/libs/optimizer/src/lib/optimizer.ts @@ -4,9 +4,11 @@ import { createSegments } from './utils/createSegments'; import { concatTracks } from './utils/concatTracks'; import { ScoringRules, scoringRules } from './scoringRules'; import { getDistance } from 'geolib'; +import { deepCopy } from '@flyxc/common'; // 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 +17,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 +108,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 +132,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 +153,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 +164,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 +172,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 +182,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 +195,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 +220,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 +252,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; } } 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);