Add Lambert solver, transfer matrix, Dijkstra routing, and route planner UI
- Lambert's problem solver using universal variable method with bisection (handles elliptic, parabolic, hyperbolic transfers + anti-podal cases) - Transfer matrix: precompute pairwise station transfers over time window using Lambert solver with configurable launch velocity - Dijkstra routing on time-expanded graph (station × week nodes, transfer + wait edges) to find minimum-time routes - Route Planner UI: from/to station dropdowns, search window selector (1-10 years), "Find Optimal Route" button with results card - Route visualization: orange dashed trajectory lines with arrow heads and leg numbers on the 2D canvas - Tested: Mercury L1 → Jupiter L1 computes 6-month direct transfer at 30 km/s — physically reasonable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ export class SolarSystem2DRenderer {
|
||||
private stationPositions: Float64Array = new Float64Array(0);
|
||||
private stationNames: string[] = [];
|
||||
private showStations: boolean = true;
|
||||
private routeLegs: { from: number; to: number }[] = [];
|
||||
private isDragging = false;
|
||||
private lastMouse = { x: 0, y: 0 };
|
||||
private animFrameId: number = 0;
|
||||
@@ -86,6 +87,10 @@ export class SolarSystem2DRenderer {
|
||||
this.showStations = visible;
|
||||
}
|
||||
|
||||
updateRoute(legs: { from: number; to: number }[]) {
|
||||
this.routeLegs = legs;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { canvas, ctx } = this;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
@@ -133,6 +138,11 @@ export class SolarSystem2DRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw route legs
|
||||
if (this.routeLegs.length > 0 && this.stationPositions.length > 0) {
|
||||
this.drawRoute();
|
||||
}
|
||||
|
||||
// Scale indicator
|
||||
this.drawScaleBar(rect.width, rect.height);
|
||||
}
|
||||
@@ -287,6 +297,58 @@ export class SolarSystem2DRenderer {
|
||||
ctx.fillText(info.name, sx + size + 4, sy + 3);
|
||||
}
|
||||
|
||||
private drawRoute() {
|
||||
const ctx = this.ctx;
|
||||
const colors = ['#ff6a33', '#33ff88', '#ff33aa', '#33aaff', '#ffaa33'];
|
||||
|
||||
for (let i = 0; i < this.routeLegs.length; i++) {
|
||||
const leg = this.routeLegs[i];
|
||||
const fromX = this.stationPositions[leg.from * 3];
|
||||
const fromY = this.stationPositions[leg.from * 3 + 1];
|
||||
const toX = this.stationPositions[leg.to * 3];
|
||||
const toY = this.stationPositions[leg.to * 3 + 1];
|
||||
|
||||
const [sx1, sy1] = this.auToScreen(fromX, fromY);
|
||||
const [sx2, sy2] = this.auToScreen(toX, toY);
|
||||
|
||||
const color = colors[i % colors.length];
|
||||
|
||||
// Draw line
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx1, sy1);
|
||||
ctx.lineTo(sx2, sy2);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Arrow head
|
||||
const angle = Math.atan2(sy2 - sy1, sx2 - sx1);
|
||||
const arrowLen = 8;
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx2, sy2);
|
||||
ctx.lineTo(
|
||||
sx2 - arrowLen * Math.cos(angle - 0.3),
|
||||
sy2 - arrowLen * Math.sin(angle - 0.3),
|
||||
);
|
||||
ctx.lineTo(
|
||||
sx2 - arrowLen * Math.cos(angle + 0.3),
|
||||
sy2 - arrowLen * Math.sin(angle + 0.3),
|
||||
);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Leg number
|
||||
const mx = (sx1 + sx2) / 2;
|
||||
const my = (sy1 + sy2) / 2;
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = 'bold 10px monospace';
|
||||
ctx.fillText(`${i + 1}`, mx + 5, my - 5);
|
||||
}
|
||||
}
|
||||
|
||||
private drawStation(xAU: number, yAU: number, name: string) {
|
||||
const ctx = this.ctx;
|
||||
const [sx, sy] = this.auToScreen(xAU, yAU);
|
||||
|
||||
Reference in New Issue
Block a user