UNPKG

igc-xc-score

Version:

igc-xc-score is a paragliding and hang-gliding XC scoring program in vanilla JS

134 lines (125 loc) 5.13 kB
'use strict'; /** * igc-xc-score Solver * scoring library for paragliding flights * * @module igc-xc-score * @author Momtchil Momtchev <momtchil@momtchev.com> */ import SortedSet from 'collections/sorted-set.js'; import { Solution } from './solution.js'; import { Range, Point } from './foundation.js'; import * as geom from './geom.js'; import * as Flight from './flight.js'; import scoringRules from '../scoring-rules.config.js'; /** * This the solver * @param {IGCFile} flight flight track data in the igc_parser format * @param {object[]} [scoringTypes=undefined] undefined for FFVL or one of the elements of scoringRules * @param {object=} config optional config parameters * @param {number=} config.maxcycle maximum execution time of the solver in ms, each sucessive call will return a better solution, default undefined for unlimited * @param {boolean=} config.noflight do not include the flight track data in the output GeoJSON, default false * @param {boolean=} config.invalid include invalid GPS fixes when evaluating the flight, default false * @param {boolean=} config.hp use high-precision distance calculation (Vincenty's), much slower for slightly higher precision, default false * @param {boolean=} config.trim automatically detect launch and landing and trim the flight track, default false */ export default function* solver(flight, _scoringTypes, _config) { let reset; const scoringTypes = _scoringTypes || scoringRules.FFVL; const config = _config || {}; Flight.analyze(flight, config); geom.init({ flight }); if (config.hp) Point.prototype.distanceEarth = Point.prototype.distanceEarthVincentys; else Point.prototype.distanceEarth = Point.prototype.distanceEarthFCC; let solutionRoots = []; for (let scoringType of scoringTypes) { for (let l of flight.ll) { const opt = { flight, launch: l.launch, landing: l.landing, scoring: scoringType, config }; let solutionRoot = new Solution([ new Range(l.launch, l.landing), new Range(l.launch, l.landing), new Range(l.launch, l.landing) ], opt); solutionRoot.do_bound(); solutionRoot.do_score(); solutionRoots.push(solutionRoot); } } let best = solutionRoots[0]; let solutionQueue = new SortedSet( solutionRoots, Solution.prototype.contentEquals, Solution.prototype.contentCompare ); let processed = 0; let tcum = 0; do { const tstart = Date.now(); while (solutionQueue.length > 0) { if (processed % 100 === 0) { if (config.env && config.env.v8 !== 'undefined') { const mem = config.env.v8.getHeapStatistics(); if (mem.used_heap_size / mem.heap_size_limit > 0.98) { /* c8 ignore next 4 */ console.error(`Out of memory: ${mem.used_heap_size/1024}KiB used` + ` of ${mem.heap_size_limit/1024}KiB total`); break; } } } let current = solutionQueue.pop(); if (current.bound <= best.score) { solutionQueue.clear(); break; } let children = current.do_branch(); for (let s of children) { s.do_bound(); if (s.bound <= best.score) continue; s.do_score(); processed++; if (s.score >= best.score && s.score > 0) { best = s; if (solutionQueue.findLeast() && solutionQueue.findLeast().value.bound <= best.score) { const garbageBest = solutionQueue.findGreatestLessThanOrEqual({ bound: best.score }); if (garbageBest !== undefined) { const cutoff = solutionQueue.indexOf(garbageBest.value); solutionQueue.splice(0, cutoff + 1).length; } } } else { delete s.scoreInfo; } solutionQueue.push(s); if (config.debug) best.last = s; } if (processed > config.maxloop || (Date.now() - tstart) > config.maxcycle) { break; } } best.processed = processed; const currentUpperBound = solutionQueue.findGreatest(); best.currentUpperBound = currentUpperBound ? currentUpperBound.value.bound : best.bound; tcum += Date.now() - tstart; best.time = tcum; if (solutionQueue.length == 0) best.optimal = true; else best.optimal = false; if (best.optimal) { reset = true; return best; } else reset = yield best; } while (!reset); }