Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

igc-xc-score integration #14

Closed
wants to merge 12 commits into from
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ srtm/
frontend/static/js/archives.js
frontend/static/js/flyxc.js
frontend/static/js/status.js
frontend/static/js/tracking.js
frontend/static/js/tracking.js
frontend/static/js/xc-score-worker.js

9 changes: 8 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:8080/?track=https%3A%2F%2Fparapente.ffvl.fr%2Fsites%2Fparapente.ffvl.fr%2Ffiles%2Figcfiles%2F2020-06-10-igcfile-276534-200361.igc&s=20&l=xc&p=u%7BpiGiuik%40%3FvfbB",
"webRoot": "${workspaceFolder}/frontend/static"
},
{
"type": "node",
"request": "attach",
Expand All @@ -12,6 +19,6 @@
"outFiles": [
"${workspaceFolder}/**/*.js"
],
}
},
]
}
63 changes: 63 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"d3-array": "^2.4.0",
"d3-scale-chromatic": "^1.5.0",
"geolib": "^3.3.1",
"igc-xc-score": "^1.5.0",
"lit-element": "^2.3.1",
"lit-html": "^1.2.1",
"pbf": "^3.2.1",
Expand Down
149 changes: 139 additions & 10 deletions frontend/src/viewer/components/path-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import { CSSResult, LitElement, PropertyValues, TemplateResult, css, customEleme
import { Measure, Point } from '../logic/score/measure';
import { RootState, store } from '../store';
import { setDistance, setLeague, setScore, setSpeed } from '../actions/map';
import { Track } from '../logic/map';

import { CircuitType } from '../logic/score/scorer';
import { CircuitType, Score } from '../logic/score/scorer';
import { ClosingSector } from '../gm/closing-sector';
import { FaiSectors } from '../gm/fai-sectors';
import { LEAGUES } from '../logic/score/league/leagues';
import { PlannerElement } from './planner-element';
import { connect } from 'pwa-helpers';
import { formatUnit } from '../logic/units';
//import { isMobileDevice } from '../logic/util';
import { BRecord, RecordExtensions } from 'igc-parser';
import { Solution } from 'igc-xc-score';
import { Units } from '../reducers/map';

const ROUTE_STROKE_COLORS = {
Expand All @@ -19,6 +23,13 @@ const ROUTE_STROKE_COLORS = {
[CircuitType.FAI_TRIANGLE]: '#ffff00',
};

const SCORE_STROKE_COLORS = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to create a route instead of having a different path ?

[CircuitType.OPEN_DISTANCE]: '#b22222',
[CircuitType.OUT_AND_RETURN]: '#b22222',
[CircuitType.FLAT_TRIANGLE]: '#cd5c5c',
[CircuitType.FAI_TRIANGLE]: '#cd5c5c',
};

const CIRCUIT_SHORT_NAME = {
[CircuitType.OPEN_DISTANCE]: 'od',
[CircuitType.OUT_AND_RETURN]: 'oar',
Expand All @@ -37,10 +48,27 @@ const WAYPOINT_FORMATS: { [id: string]: string } = {
@customElement('path-ctrl-element')
export class PathCtrlElement extends connect(store)(LitElement) {
line: google.maps.Polyline | null = null;
scoring: {
path: google.maps.Polyline | null;
closing: google.maps.Polyline | null;
} = { path: null, closing: null };

@property({ attribute: false })
expanded = false;


@property({ attribute: false })
tracks: Track[] | null = null;

@property({ attribute: false })
currentTrack: number | null = null;

@property({ attribute: false })
worker: Worker | null = null;

@property({ attribute: false })
measureIcon: string = 'img/measuring.svg';

@property({ attribute: false })
units: Units | null = null;

Expand Down Expand Up @@ -104,6 +132,8 @@ export class PathCtrlElement extends connect(store)(LitElement) {
this.speed = state.map.speed;
this.league = state.map.league;
this.units = state.map.units;
this.tracks = state.map.tracks;
this.currentTrack = state.map.currentTrack;
}
}

Expand Down Expand Up @@ -176,14 +206,10 @@ export class PathCtrlElement extends connect(store)(LitElement) {
} else {
const line = this.line as google.maps.Polyline;
line.setMap(null);
if (this.flight) {
this.flight.setMap(null);
}
if (this.closingSector) {
this.closingSector.setMap(null);
}
if (this.faiSectors) {
this.faiSectors.setMap(null);
for (let e of [this.flight, this.closingSector, this.faiSectors, this.scoring.closing, this.scoring.path]) {
if (e) {
e.setMap(null);
}
}
store.dispatch(setScore(null));
google.maps.event.removeListener(this.onAddPoint as google.maps.MapsEventListener);
Expand Down Expand Up @@ -333,13 +359,116 @@ export class PathCtrlElement extends connect(store)(LitElement) {
}
}

protected launchScoring(): void {
if (this.tracks && this.currentTrack !== null && this.tracks[this.currentTrack]) {
const fixes: BRecord[] = [];
for (let i = 0; i < this.tracks[this.currentTrack].fixes.lat.length; i++)
// Keep this to the bare minimum needed for igc-xc-score
fixes[i] = {
timestamp: this.tracks[this.currentTrack].fixes.ts[i],
latitude: this.tracks[this.currentTrack].fixes.lat[i],
longitude: this.tracks[this.currentTrack].fixes.lon[i],
pressureAltitude: this.tracks[this.currentTrack].fixes.alt[i],
gpsAltitude: this.tracks[this.currentTrack].fixes.alt[i],
valid: true,
Copy link
Owner

@vicb vicb Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to specify all the records that have a constant value ? (valid -> enl)

extensions: {} as RecordExtensions,
fixAccuracy: null,
time: '',
enl: null
};
if (this.worker !== null)
this.worker.terminate();
this.worker = new Worker('js/xc-score-worker.js');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to create a new Worker each time ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not that expensive, this is only when the user clicks the button. terminate allows the user to relaunch a new optimization even when the previous hasn't finished. Interrupting a running optimization and reusing the thread would add lots of complexity for a very marginal performance gain - currently once an optimization is launched, the thread won't yield the CPU until it is finished.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense.
If the worked is not re-used then we can release it when it's done (this.worker = null).

this.worker.onmessage = this.updateScore.bind(this);
this.worker.postMessage({ msg: 'xc-score-start', flight: JSON.stringify(fixes), league: this.league });
Copy link
Owner

@vicb vicb Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to use a protobuf (ArrayBuffer) instead of JSON.
It is faster to encode/decode and less memory intensive (does not encoded the field names).
The only thing is that we would need the [en/de]coding logic on the worker side which is ~3kB - probably not a big deal once the worker is cached.

(Note: this is a nice to have, not required for the optimization to be merged).

this.measureIcon = 'img/pacman.svg';
}
}

protected updateScore(msg: any): void {
if (msg.data.msg && (msg.data.msg === 'xc-score-result' || msg.data.msg === 'xc-score-progress')) {
const r = JSON.parse(msg.data.r) as Solution;
let t: CircuitType;
let closedCircuit: boolean = false;
switch (r.opt.scoring.code) {
case 'tri':
t = CircuitType.FLAT_TRIANGLE;
closedCircuit = true;
break;
case 'fai':
t = CircuitType.FAI_TRIANGLE;
closedCircuit = true;
break;
default:
case 'od':
t = CircuitType.OPEN_DISTANCE;
break;
}
const score: Score = {
points: r.score ? r.score : 0,
distance: r.scoreInfo ? r.scoreInfo.distance * 1000 : 0,
closingRadius: r.scoreInfo && r.scoreInfo.cp ? r.scoreInfo.cp.d : 0,
multiplier: r.opt.scoring.multiplier,
circuit: t,
indexes: [0]
}

if (this.scoring.path !== null)
this.scoring.path.setMap(null);
if (this.scoring.closing !== null)
this.scoring.closing.setMap(null);
let turnPoints: google.maps.LatLng[] = [];
if (r.scoreInfo && r.scoreInfo.tp) {
turnPoints = r.scoreInfo.tp.map(p => new google.maps.LatLng(p.y, p.x));
if (closedCircuit) {
/* Triangle -> first point is also last */
turnPoints.push(turnPoints[0]);
} else {
/* Open distance -> ep.start and ep.finish are actually first and fifth turnpoint */
if (r.scoreInfo && r.scoreInfo.ep) {
turnPoints.unshift(new google.maps.LatLng(r.scoreInfo.ep.start.y, r.scoreInfo.ep.start.x));
turnPoints.push(new google.maps.LatLng(r.scoreInfo.ep.finish.y, r.scoreInfo.ep.finish.x));
}
}
this.scoring.path = new google.maps.Polyline({
map: this.map as google.maps.Map,
path: turnPoints,
strokeColor: SCORE_STROKE_COLORS[score.circuit],
strokeWeight: 4,
zIndex: 1000,
});
}
let closingPoints: google.maps.LatLng[] = [];
if (r.scoreInfo && r.scoreInfo.cp && closedCircuit) {
closingPoints = [new google.maps.LatLng(r.scoreInfo.cp.in.y, r.scoreInfo.cp.in.x),
new google.maps.LatLng(r.scoreInfo.cp.out.y, r.scoreInfo.cp.out.x)];
this.scoring.closing = new google.maps.Polyline({
map: this.map as google.maps.Map,
path: closingPoints,
strokeColor: SCORE_STROKE_COLORS['Out and return'],
strokeWeight: 4,
zIndex: 1000,
});
}

store.dispatch(setScore(score));
if (msg.data.msg === 'xc-score-result')
this.measureIcon = 'img/measuring.svg';
}
}

protected render(): TemplateResult {
// Update the URL on re-rendering
this.getQrText();
return this.units
? html`
<link rel="stylesheet" href="https://kit-free.fontawesome.com/releases/latest/css/free.min.css" />
<span .hidden=${!this.expanded}>${formatUnit(this.distance, this.units.distance)}</span>
<span .hidden=${!this.expanded}>
<i class="fas fa-2x" style="cursor: pointer" @click=${this.launchScoring}>
<img width="32" height="32" style="vertical-align: middle;" src="${this.measureIcon}" />
</i>
${formatUnit(this.distance, this.units.distance)}
</span>
<i class="fas fa-ruler fa-2x" style="cursor: pointer" @click=${this.toggleExpanded}></i>

<ui5-dialog id="share-dialog" header-text="Share">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/viewer/components/tracking-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export class TrackingElement extends connect(store)(LitElement) {
this.features = map.data.addGeoJson(geoJson);
features.forEach((f) => map.data.remove(f));
}
});
})
.catch((e) => { console.error(e); return null; });
}

protected setupListener(map: google.maps.Map): void {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/viewer/logic/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isMobileDevice() {
return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
}
Loading