259 lines
11 KiB
TypeScript
259 lines
11 KiB
TypeScript
import type { Body } from "../calculations/constants";
|
|
import { extrapolateTrajectory, findOrbitThroughInterpolation, type OrbitalCoordinates } from "../calculations/orbit-calculations";
|
|
import type { ChangingStorageValue } from "../storage";
|
|
import { createLabel, createNumberInput, createRadioButton, getCoordinatesFromParameters, type OrbitalParameters } from "./common";
|
|
import { Renderer } from "./renderer";
|
|
|
|
export interface InterpolationParameters {
|
|
targetPeriapsis: number,
|
|
orbitChoice: "apoapsis" | "speedAndAltitude",
|
|
targetApoapsis: number,
|
|
targetSpeed: number,
|
|
targetAltitude: number,
|
|
firstOwnAltitude: number,
|
|
firstTargetAltitude: number,
|
|
firstDistance: number,
|
|
firstPhaseAngle: number,
|
|
secondOwnAltitude: number,
|
|
secondTargetAltitude: number,
|
|
secondDistance: number,
|
|
secondPhaseAngle: number,
|
|
}
|
|
|
|
export const DefaultInterpolationParameters: InterpolationParameters = {
|
|
targetPeriapsis: 100000,
|
|
orbitChoice: "apoapsis",
|
|
targetApoapsis: 200000,
|
|
targetSpeed: 0,
|
|
targetAltitude: 0,
|
|
firstOwnAltitude: 0,
|
|
firstTargetAltitude: 0,
|
|
firstDistance: 0,
|
|
firstPhaseAngle: 0,
|
|
secondOwnAltitude: 0,
|
|
secondTargetAltitude: 0,
|
|
secondDistance: 0,
|
|
secondPhaseAngle: 0
|
|
}
|
|
|
|
export class InterpolateOrbitGui {
|
|
parentDiv: HTMLDivElement;
|
|
startingOrbitalParameters: ChangingStorageValue<OrbitalParameters>;
|
|
body: ChangingStorageValue<Body>;
|
|
interpolationParameters: ChangingStorageValue<InterpolationParameters>;
|
|
interpolatedOrbitCoordinates: ChangingStorageValue<OrbitalCoordinates | null>;
|
|
interpolatedOrbitTime: ChangingStorageValue<number | null>;
|
|
sourceId: string;
|
|
renderDiv: HTMLDivElement;
|
|
|
|
constructor(interpolationParameters: ChangingStorageValue<InterpolationParameters>, startingOrbitParameters: ChangingStorageValue<OrbitalParameters>, interpolatedOrbitCoordinates: ChangingStorageValue<OrbitalCoordinates | null>, interpolatedOrbitTime: ChangingStorageValue<number | null>, body: ChangingStorageValue<Body>) {
|
|
this.parentDiv = document.createElement("div");
|
|
this.startingOrbitalParameters = startingOrbitParameters;
|
|
this.interpolationParameters = interpolationParameters;
|
|
this.interpolatedOrbitCoordinates = interpolatedOrbitCoordinates;
|
|
this.interpolatedOrbitTime = interpolatedOrbitTime;
|
|
this.renderDiv = document.createElement("div");
|
|
this.body = body;
|
|
|
|
let targetOrbitHeader = document.createElement("h4");
|
|
targetOrbitHeader.appendChild(document.createTextNode("Describe target orbit:"));
|
|
this.parentDiv.appendChild(targetOrbitHeader);
|
|
|
|
let addToParent = (elements: HTMLElement[]) => {
|
|
let container = document.createElement("div");
|
|
container.classList.add("orbitalParameter");
|
|
elements.forEach(child => container.appendChild(child));
|
|
this.parentDiv.appendChild(container);
|
|
}
|
|
|
|
let createInputWithLabel = (label: string, minimum: number, maximum?: number, size?: number) => {
|
|
let inputId = crypto.randomUUID();
|
|
let labelElement = createLabel(inputId, label);
|
|
let numberInput = createNumberInput(inputId, minimum, maximum, size);
|
|
addToParent([labelElement, numberInput]);
|
|
|
|
return numberInput;
|
|
}
|
|
|
|
let periapsisInput = createInputWithLabel("Target peripasis:", 0);
|
|
|
|
let descriptorChoiceId = crypto.randomUUID();
|
|
let apoapsisButtonId = crypto.randomUUID();
|
|
let apoapsisButton = createRadioButton(descriptorChoiceId, apoapsisButtonId);
|
|
let apoapsisButtonLabel = createLabel(apoapsisButtonId, "Use apoapsis");
|
|
|
|
let speedAndAltitudeId = crypto.randomUUID();
|
|
let speedAndAltitudeButton = createRadioButton(descriptorChoiceId, speedAndAltitudeId);
|
|
let speedAndAltitudeLabel = createLabel(speedAndAltitudeId, "Use speed and altitude");
|
|
addToParent([apoapsisButton, apoapsisButtonLabel, speedAndAltitudeButton, speedAndAltitudeLabel]);
|
|
|
|
let apoapsisInput = createInputWithLabel("Target apopasis:", 0);
|
|
let speedInput = createInputWithLabel("Target speed:", 0);
|
|
let altitudeInput = createInputWithLabel("Target altitude:", 0);
|
|
|
|
let instantOneHeader = document.createElement("h4");
|
|
instantOneHeader.appendChild(document.createTextNode("Measurement one:"));
|
|
this.parentDiv.appendChild(instantOneHeader);
|
|
|
|
let firstOwnAltitudeInput = createInputWithLabel("Own altitude:", 0);
|
|
let firstTargetAltitudeInput = createInputWithLabel("Target altitude:", 0);
|
|
let firstDistanceInput = createInputWithLabel("Distance to target:", 0);
|
|
let firstPhaseAngleInput = createInputWithLabel("Phase angle:", -180, 180);
|
|
|
|
let instantTwoHeader = document.createElement("h4");
|
|
instantTwoHeader.appendChild(document.createTextNode("Measurement two:"));
|
|
this.parentDiv.appendChild(instantTwoHeader);
|
|
|
|
let secondOwnAltitudeInput = createInputWithLabel("Own altitude:", 0);
|
|
let secondTargetAltitudeInput = createInputWithLabel("Target altitude:", 0);
|
|
let secondDistanceInput = createInputWithLabel("Distance to target:", 0);
|
|
let secondPhaseAngleInput = createInputWithLabel("Phase angle:", -180, 180);
|
|
|
|
let interpolateOrbitButton = document.createElement("button");
|
|
interpolateOrbitButton.appendChild(document.createTextNode("Find interpolated orbit"));
|
|
this.parentDiv.appendChild(interpolateOrbitButton);
|
|
this.parentDiv.appendChild(this.renderDiv);
|
|
|
|
this.sourceId = crypto.randomUUID();
|
|
const onChange = () => {
|
|
let periapsis = parseFloat(periapsisInput.value);
|
|
let apoapsis = parseFloat(apoapsisInput.value);
|
|
let speed = parseFloat(speedInput.value);
|
|
let altitude = parseFloat(altitudeInput.value);
|
|
let firstTargetAltitude = parseFloat(firstTargetAltitudeInput.value);
|
|
let firstOwnAltitude = parseFloat(firstOwnAltitudeInput.value);
|
|
let firstDistance = parseFloat(firstDistanceInput.value);
|
|
let firstPhaseAngle = parseFloat(firstPhaseAngleInput.value) * Math.PI / 180.0;
|
|
let secondTargetAltitude = parseFloat(secondTargetAltitudeInput.value);
|
|
let secondOwnAltitude = parseFloat(secondOwnAltitudeInput.value);
|
|
let secondDistance = parseFloat(secondDistanceInput.value);
|
|
let secondPhaseAngle = parseFloat(secondPhaseAngleInput.value) * Math.PI / 180.0;
|
|
|
|
let orbitChoice: "apoapsis" | "speedAndAltitude" = "apoapsis";
|
|
if (speedAndAltitudeButton.checked) {
|
|
orbitChoice = "speedAndAltitude"
|
|
};
|
|
|
|
interpolationParameters.set({
|
|
targetPeriapsis: periapsis,
|
|
orbitChoice: orbitChoice,
|
|
targetApoapsis: apoapsis,
|
|
targetSpeed: speed,
|
|
targetAltitude: altitude,
|
|
firstTargetAltitude: firstTargetAltitude,
|
|
firstOwnAltitude: firstOwnAltitude,
|
|
firstDistance: firstDistance,
|
|
firstPhaseAngle: firstPhaseAngle,
|
|
secondTargetAltitude: secondTargetAltitude,
|
|
secondOwnAltitude: secondOwnAltitude,
|
|
secondDistance: secondDistance,
|
|
secondPhaseAngle: secondPhaseAngle
|
|
}, this.sourceId)
|
|
};
|
|
|
|
[
|
|
periapsisInput,
|
|
apoapsisButton,
|
|
speedAndAltitudeButton,
|
|
apoapsisInput,
|
|
speedInput,
|
|
altitudeInput,
|
|
firstOwnAltitudeInput,
|
|
firstTargetAltitudeInput,
|
|
firstDistanceInput,
|
|
firstPhaseAngleInput,
|
|
secondOwnAltitudeInput,
|
|
secondTargetAltitudeInput,
|
|
secondDistanceInput,
|
|
secondPhaseAngleInput
|
|
].forEach(input => input.addEventListener("change", onChange));
|
|
|
|
interpolationParameters.listenToValue((source, value) => {
|
|
if (value.orbitChoice == "apoapsis") {
|
|
apoapsisInput.removeAttribute("disabled");
|
|
speedInput.setAttribute("disabled", "");
|
|
altitudeInput.setAttribute("disabled", "");
|
|
apoapsisButton.setAttribute("checked", "");
|
|
} else if (value.orbitChoice == "speedAndAltitude") {
|
|
apoapsisInput.setAttribute("disabled", "");
|
|
speedInput.removeAttribute("disabled");
|
|
altitudeInput.removeAttribute("disabled");
|
|
speedAndAltitudeButton.setAttribute("checked", "");
|
|
}
|
|
|
|
if (source == this.sourceId) {
|
|
return;
|
|
}
|
|
|
|
periapsisInput.value = value.targetPeriapsis.toString();
|
|
apoapsisInput.value = value.targetApoapsis.toString();
|
|
speedInput.value = value.targetSpeed.toString();
|
|
altitudeInput.value = value.targetAltitude.toString();
|
|
firstOwnAltitudeInput.value = value.firstOwnAltitude.toString();
|
|
firstTargetAltitudeInput.value = value.firstTargetAltitude.toString();
|
|
firstDistanceInput.value = value.firstDistance.toString();
|
|
firstPhaseAngleInput.value = (value.firstPhaseAngle * 180 / Math.PI).toString();
|
|
|
|
secondOwnAltitudeInput.value = value.secondOwnAltitude.toString();
|
|
secondTargetAltitudeInput.value = value.secondTargetAltitude.toString();
|
|
secondDistanceInput.value = value.secondDistance.toString();
|
|
secondPhaseAngleInput.value = (value.secondPhaseAngle * 180 / Math.PI).toString();
|
|
});
|
|
|
|
|
|
// Interpolation action
|
|
interpolateOrbitButton.addEventListener("click", _ => {
|
|
this.renderDiv.innerHTML = "";
|
|
|
|
let startingOrbitalParameters = this.startingOrbitalParameters.getCurrentValue();
|
|
let interpolationParameters = structuredClone(this.interpolationParameters.getCurrentValue());
|
|
let body = this.body.getCurrentValue();
|
|
|
|
// All interpolation altitude parameters need to get the planet radius added to them
|
|
interpolationParameters.firstTargetAltitude += body.radius;
|
|
interpolationParameters.firstOwnAltitude += body.radius;
|
|
interpolationParameters.secondTargetAltitude += body.radius;
|
|
interpolationParameters.secondOwnAltitude += body.radius;
|
|
interpolationParameters.targetAltitude += body.radius;
|
|
interpolationParameters.targetApoapsis += body.radius;
|
|
interpolationParameters.targetPeriapsis += body.radius;
|
|
|
|
let ownCoordinates = getCoordinatesFromParameters(startingOrbitalParameters, body);
|
|
let results = findOrbitThroughInterpolation(ownCoordinates, interpolationParameters, body);
|
|
|
|
if (results.length == 1) {
|
|
this.interpolatedOrbitCoordinates.set(results[0][0], "null");
|
|
this.interpolatedOrbitTime.set(results[0][1], "null");
|
|
} else {
|
|
let chooseHeader = document.createElement("h3");
|
|
chooseHeader.appendChild(document.createTextNode("Choose interpolated orbit:"));
|
|
this.renderDiv.appendChild(chooseHeader);
|
|
|
|
let interpolationChoiceId = crypto.randomUUID();
|
|
results.forEach(([coordinates, time]) => {
|
|
let buttonId = crypto.randomUUID();
|
|
let button = createRadioButton(interpolationChoiceId, buttonId);
|
|
let label = createLabel(buttonId, "The orbit below fits best");
|
|
let renderer = new Renderer(body);
|
|
let extrapolatedOwnCoordinates = extrapolateTrajectory(time, ownCoordinates, body);
|
|
let usedCoordinates = ownCoordinates;
|
|
if (extrapolatedOwnCoordinates) {
|
|
usedCoordinates = extrapolatedOwnCoordinates;
|
|
}
|
|
renderer.addCoordinates(usedCoordinates, 0x00ffff);
|
|
renderer.addCoordinates(coordinates, 0xff00ff);
|
|
|
|
this.renderDiv.appendChild(button);
|
|
this.renderDiv.appendChild(label);
|
|
this.renderDiv.appendChild(renderer.parentDiv);
|
|
this.renderDiv.appendChild(document.createElement("br"));
|
|
|
|
button.addEventListener("change", _ => {
|
|
this.interpolatedOrbitCoordinates.set(coordinates, "null");
|
|
this.interpolatedOrbitTime.set(startingOrbitalParameters.currentTimeAtReading + time, "null");
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
} |