mirror of
https://github.com/Krafpy/KSP-MGA-Planner.git
synced 2025-12-12 07:40:41 -08:00
Merge pull request #33 from Krafpy/csv-trajectory-data
Added CSV data download button.
This commit is contained in:
16
dist/main/editor/editor.js
vendored
16
dist/main/editor/editor.js
vendored
@@ -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();
|
||||
|
||||
6
dist/main/objects/orbit.js
vendored
6
dist/main/objects/orbit.js
vendored
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
46
dist/main/utilities/trajectory-text.js
vendored
46
dist/main/utilities/trajectory-text.js
vendored
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
20
style.css
20
style.css
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user