Merge pull request #33 from Krafpy/csv-trajectory-data

Added CSV data download button.
This commit is contained in:
Krafpy
2023-01-08 00:18:27 +01:00
committed by GitHub
8 changed files with 181 additions and 4 deletions

View File

@@ -15,7 +15,7 @@ import { Trajectory } from "../solvers/trajectory.js";
import { Selector } from "./selector.js";
import { DiscreteRange } from "./range.js";
import { loadBodiesData, loadConfig } from "../utilities/data.js";
import { trajectoryToText } from "../utilities/trajectory-text.js";
import { trajectoryToCSVData, trajectoryToText } from "../utilities/trajectory-text.js";
import { DraggableTextbox } from "./draggable-text.js";
export async function initEditorWithSystem(systems, systemIndex) {
const canvas = document.getElementById("three-canvas");
@@ -156,6 +156,8 @@ export async function initEditorWithSystem(systems, systemIndex) {
const stepSlider = new DiscreteRange("displayed-steps-slider");
const showTrajDetailsBtn = new Button("show-text-btn");
showTrajDetailsBtn.disable();
const downloadTrajDataBtn = new Button("download-csv-btn");
downloadTrajDataBtn.disable();
detailsSelector.disable();
stepSlider.disable();
const getSpan = (id) => document.getElementById(id);
@@ -187,6 +189,7 @@ export async function initEditorWithSystem(systems, systemIndex) {
detailsSelector.disable();
stepSlider.disable();
showTrajDetailsBtn.disable();
downloadTrajDataBtn.disable();
if (trajectory)
trajectory.remove();
};
@@ -211,6 +214,17 @@ export async function initEditorWithSystem(systems, systemIndex) {
DraggableTextbox.create(`Trajectory ${trajectoryCounter}`, trajText);
});
showTrajDetailsBtn.enable();
const trajCSV = trajectoryToCSVData(trajectory);
downloadTrajDataBtn.click(() => {
let element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(trajCSV));
element.setAttribute('download', `trajectory-${trajectoryCounter}.csv`);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
});
downloadTrajDataBtn.enable();
};
const findTrajectory = async () => {
paramsErr.hide();

View File

@@ -108,4 +108,10 @@ export class Orbit {
radius(trueAnomaly) {
return this.orbitalParam / (1 + this.eccentricity * Math.cos(trueAnomaly));
}
stateAtDate(meanAnomaly0, epoch, date) {
const nu = this.solveTrueAnomalyAtDate(meanAnomaly0, epoch, date);
const pos = this.positionFromTrueAnomaly(nu);
const vel = this.velocityFromTrueAnomaly(nu);
return { pos, vel };
}
}

View File

@@ -91,3 +91,49 @@ function pairsToString(pairs) {
}
return joinStrings(lines, "\n");
}
export function trajectoryToCSVData(traj) {
const { config, steps, orbits } = traj;
const n = steps.length;
const entries = [];
const m = steps[n - 1].flyby !== undefined ? n - 1 : n - 2;
for (let i = 2; i < m; i++) {
const orbit = orbits[i];
const step = steps[i];
const { maneuvre, flyby, dateOfStart, startM } = step;
if (maneuvre && maneuvre.context.type == "dsm") {
const startState = orbit.stateAtDate(startM, 0, 0);
entries.push({
type: "dsm",
bodyId: step.attractorId,
timeUT: KSPTime(dateOfStart, config.time).dateSeconds,
pos: startState.pos,
vel: startState.vel
});
}
else if (!maneuvre && !flyby) {
const startState = orbit.stateAtDate(startM, 0, 0);
entries.push({
type: "flyby",
bodyId: steps[i - 1].attractorId,
timeUT: KSPTime(dateOfStart, config.time).dateSeconds,
pos: startState.pos,
vel: startState.vel
});
}
}
entries[0].type = "escape";
const arrivalDate = steps[m - 1].dateOfStart + steps[m - 1].duration;
const arrivalState = orbits[m - 1].stateAtDate(steps[m - 1].startM, 0, arrivalDate);
entries.push({
type: "arrival",
bodyId: steps[n - 1].attractorId,
timeUT: KSPTime(arrivalDate, config.time).dateSeconds,
pos: arrivalState.pos,
vel: arrivalState.vel
});
const lines = ["type,bodyId,timeUT,posX,posY,posZ,velX,velY,velZ"];
for (const { type, bodyId, timeUT, pos, vel } of entries) {
lines.push(`${type},${bodyId},${timeUT},${pos.x},${pos.y},${pos.z},${vel.x},${vel.y},${vel.z}`);
}
return joinStrings(lines, "\n");
}

View File

@@ -242,7 +242,10 @@
<div id="result-panel-header-left">
<h2 class="calculation-panel-title">Calculated trajectory</h2>
<button id="show-text-btn" class="submit-btn" disabled>
<i class="fa-solid fa-circle-info"></i> Steps
<i class="fa-solid fa-circle-info"></i> Details
</button>
<button id="download-csv-btn" class="submit-btn" disabled>
<i class="fa-solid fa-file-arrow-down"></i> Data
</button>
</div>
<div id="steps-slider-control-group" class="control-group">

View File

@@ -16,7 +16,7 @@ import { Selector } from "./selector.js";
import { DiscreteRange } from "./range.js";
import { OrbitingBody } from "../objects/body.js";
import { loadBodiesData, loadConfig } from "../utilities/data.js";
import { trajectoryToText } from "../utilities/trajectory-text.js";
import { trajectoryToCSVData, trajectoryToText } from "../utilities/trajectory-text.js";
import { DraggableTextbox } from "./draggable-text.js";
@@ -214,6 +214,8 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
const showTrajDetailsBtn = new Button("show-text-btn");
showTrajDetailsBtn.disable();
const downloadTrajDataBtn = new Button("download-csv-btn");
downloadTrajDataBtn.disable();
detailsSelector.disable();
stepSlider.disable();
@@ -249,6 +251,7 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
detailsSelector.disable();
stepSlider.disable();
showTrajDetailsBtn.disable();
downloadTrajDataBtn.disable();
if(trajectory) trajectory.remove();
}
@@ -278,6 +281,18 @@ export async function initEditorWithSystem(systems: SolarSystemData[], systemInd
DraggableTextbox.create(`Trajectory ${trajectoryCounter}`, trajText);
});
showTrajDetailsBtn.enable();
const trajCSV = trajectoryToCSVData(trajectory);
downloadTrajDataBtn.click(() => {
let element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(trajCSV));
element.setAttribute('download', `trajectory-${trajectoryCounter}.csv`);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
});
downloadTrajDataBtn.enable();
};
const findTrajectory = async () => {

View File

@@ -165,4 +165,18 @@ export class Orbit implements IOrbit {
public radius(trueAnomaly: number) {
return this.orbitalParam / (1 + this.eccentricity * Math.cos(trueAnomaly));
}
/**
* Computes the position and velocity on the orbit at a specific date.
* @param meanAnomaly0 The reference mean anomaly.
* @param epoch The date at which the reference mean anomaly corresponds to.
* @param date The date at which compute the position.
* @returns The position and velocity on the orbit at the specified date.
*/
public stateAtDate(meanAnomaly0: number, epoch: number, date: number){
const nu = this.solveTrueAnomalyAtDate(meanAnomaly0, epoch, date);
const pos = this.positionFromTrueAnomaly(nu);
const vel = this.velocityFromTrueAnomaly(nu);
return {pos, vel};
}
}

View File

@@ -106,3 +106,64 @@ function pairsToString(pairs: pair[]){
}
return joinStrings(lines, "\n");
}
type CSVEntry = {
type: "escape" | "arrival" | "flyby" | "dsm",
bodyId: number,
timeUT: number,
pos: {x: number, y: number, z: number},
vel: {x: number, y: number, z: number},
};
export function trajectoryToCSVData(traj: Trajectory){
const {config, steps, orbits} = traj;
const n = steps.length;
const entries: CSVEntry[] = [];
const m = steps[n-1].flyby !== undefined ? n-1 : n-2;
for(let i = 2; i < m; i++){
const orbit = orbits[i];
const step = steps[i];
const {maneuvre, flyby, dateOfStart, startM} = step;
if(maneuvre && maneuvre.context.type == "dsm"){
const startState = orbit.stateAtDate(startM, 0, 0);
entries.push({
type: "dsm",
bodyId: step.attractorId,
timeUT: KSPTime(dateOfStart, config.time).dateSeconds,
pos: startState.pos,
vel: startState.vel
});
} else if(!maneuvre && !flyby) {
const startState = orbit.stateAtDate(startM, 0, 0);
entries.push({
type: "flyby",
bodyId: steps[i-1].attractorId,
timeUT: KSPTime(dateOfStart, config.time).dateSeconds,
pos: startState.pos,
vel: startState.vel
});
}
}
entries[0].type = "escape";
const arrivalDate = steps[m-1].dateOfStart + steps[m-1].duration;
const arrivalState = orbits[m-1].stateAtDate(steps[m-1].startM, 0, arrivalDate);
entries.push({
type: "arrival",
bodyId: steps[n-1].attractorId,
timeUT: KSPTime(arrivalDate, config.time).dateSeconds,
pos: arrivalState.pos,
vel: arrivalState.vel
});
const lines: string[] = ["type,bodyId,timeUT,posX,posY,posZ,velX,velY,velZ"];
for(const {type, bodyId, timeUT, pos, vel} of entries){
lines.push(
`${type},${bodyId},${timeUT},${pos.x},${pos.y},${pos.z},${vel.x},${vel.y},${vel.z}`
);
}
return joinStrings(lines, "\n");
}

View File

@@ -491,18 +491,36 @@ input[type="range"]
background-image: linear-gradient(rgb(0, 93, 163), rgb(0, 60, 105));
}
#download-csv-btn
{
margin-left: 5px;
background-image: linear-gradient(rgb(0, 137, 57), rgb(0, 87, 46));
}
#show-text-btn:hover
{
background-image: linear-gradient(rgb(0, 78, 138), rgb(0, 60, 105));
}
#download-csv-btn:hover
{
background-image: linear-gradient(rgb(0, 118, 49), rgb(0, 87, 46));
}
#show-text-btn:active
{
background-image: none;
background-color: rgb(0, 60, 105);
}
#show-text-btn:disabled
#download-csv-btn:active
{
background-image: none;
background-color: rgb(0, 87, 46);
}
#show-text-btn:disabled, #download-csv-btn:disabled
{
background-image: none;
background-color: rgb(46, 52, 56);