1
0
Fork 0

Phi: Add Holding Pattern Tool

A simple tool to visualize holding pattern entries
This commit is contained in:
Torsten Dreyer 2015-02-19 10:12:09 +01:00
parent 3b44cbd6bd
commit c295450e7d
4 changed files with 273 additions and 1 deletions

View file

@ -2,6 +2,10 @@ define([
'knockout', 'text!./Tools.html' 'knockout', 'text!./Tools.html'
], function(ko, htmlString) { ], function(ko, htmlString) {
ko.components.register('Tools/Holding Pattern', {
require : 'topics/Tools/Holding'
});
ko.components.register('Tools/Stopwatch', { ko.components.register('Tools/Stopwatch', {
require : 'topics/Tools/Stopwatch' require : 'topics/Tools/Stopwatch'
}); });

View file

@ -0,0 +1,105 @@
<div>
<style>
.holding-patter-navaid {
stroke: none;
fill: cyan;
}
.holding-pattern-direct {
fill: green;
}
.holding-pattern-teardrop {
fill: red;
}
.holding-pattern-parallel {
fill: blue;
}
.holding-pattern-legend {
fill: white;
font-size: 7px;
text-anchor: middle;
}
.holding-pattern-racetrack {
fill: none;
stroke-width: 1px;
stroke: yellow;
}
.holding-pattern-heading {
fill: none;
stroke-width: 2px;
stroke: #c0c0c0;
stroke-linecap: round;
stroke-linejoin: miter;
stroke-dasharray: 2, 3;
}
</style>
<fieldset>
<legend>Holding Pattern</legend>
<table>
<tbody>
<tr>
<td>Inbound Track</td>
<td><input data-bind="spinner: { value: inboundTrack, spin: inboundTrackSpin }"></td>
<td rowspan="3">
<div style="width: 300px; height: 300px; padding-left: 5em;">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100 100"
preserveAspectRatio="xMinYMin meet">
<g data-bind="attr: { transform: holdingTransform }">
<path d="M50,50 l37.58770483143634,13.680805733026748 A40,40, 0 0,1 12.41229516856366,36.31919426697327 z"
class="holding-pattern-direct" />
<path d="M50,50 l-37.587704831436334,-13.680805733026759 A40,40, 0 0,1 49.99999999999999,10 z"
class="holding-pattern-teardrop" />
<path d="M50,50 l-9.797174393178826e-15,-40 A40,40, 0 0,1 87.58770483143635,63.68080573302672 z"
class="holding-pattern-parallel" />
<path d="M 50 50 a 7.5 7.5 0 0 1 15 0 v30 a 7.5 7.5 0 0 1 -15 0 z M50 55 l 2 5 h -4 z M65 75 l -2 -5 h4 z" class="holding-pattern-racetrack" />
</g>
<g data-bind="attr: { transform: holdingTransform }, visible: nonStandard">
<path d="M50,50 l37.58770483143634,-13.680805733026748 A40,40, 0 0,1 12.41229516856366,63.6808057331 z"
class="holding-pattern-direct" />
<path d="M50,50 l0,-40 A40,40, 0 0,1 87.58770483143635,36.31919426697327 z" class="holding-pattern-teardrop" />
<path d="M50,50 l-37.587704831436334,13.680805733026759 A40,40, 0 0,1 50,10 z" class="holding-pattern-parallel" />
<path d="M 50 50 a 7.5 7.5 0 0 0 -15 0 v 30 a 7.5 7.5 0 0 0 15 0 z M50 55 l 2 5 h -4 z M35 75 l -2 -5 h4 z" class="holding-pattern-racetrack" />
</g>
<circle cx="50" cy="50" r="2.5" class="holding-patter-navaid" />
<g data-bind="attr: { transform: trackTransform }">
<path data-bind="attr: { d: trackDraw }" class="holding-pattern-heading" />
</g>
<rect x="0" y="92" rx="2" ry="2" width="30" height="8" class="holding-pattern-direct" />
<text x="15" y="98" class="holding-pattern-legend">direct</text>
<rect x="35" y="92" rx="2" ry="2" width="30" height="8" class="holding-pattern-teardrop" />
<text x="50" y="98" class="holding-pattern-legend">teardrop</text>
<rect x="70" y="92" rx="2" ry="2" width="30" height="8" class="holding-pattern-parallel" />
<text x="85" y="98" class="holding-pattern-legend">parallel</text>
</svg>
</div>
</td>
</tr>
<tr>
<td>Heading</td>
<td><input data-bind="spinner: { value: heading, spin: headingSpin }"></td>
</tr>
<tr>
<td colspan="2"><input id="holding-type-nonstandard" type="radio" name="holdingtype"
data-bind="button: {}, event: { change: setNonStandard }"> <label for="holding-type-nonstandard">Non
Standard (Left)</label> <input id="holding-type-standard" type="radio" name="holdingtype" checked="checked"
data-bind="button: {}, event: { change: setStandard }"> <label for="holding-type-standard">Standard
(Right)</label></td>
</tr>
</tbody>
</table>
</fieldset>
<!--
<div data-bind="text: entry"></div>
<div data-bind="text: ko.toJSON($data)"></div>
-->
</div>

View file

@ -0,0 +1,163 @@
define([
'jquery', 'knockout', 'text!./Holding.html', 'sprintf', 'kojqui/button', 'kojqui/spinner'
], function(jquery, ko, htmlString, sprintf) {
function ViewModel(params) {
var self = this;
function normDeg(val, min, max) {
var d = max - min;
while (val >= max)
val -= d;
while (val < min)
val += d;
return val;
}
self.standard = ko.observable(true);
self.nonStandard = ko.pureComputed(function() {
return false == self.standard();
});
self.inboundTrack = ko.observable(0);
self.heading = ko.observable(270);
self.entry = ko.observable("");
self.entryClass = function(p) {
console.log(p, self.entry());
if (p == self.entry())
return 'holding-pattern-' + p;
else
return 'active-holding-pattern-' + p;
}
self.holdingTransform = ko.pureComputed(function() {
return sprintf.sprintf("rotate(%f 50 50)", self.inboundTrack());
});
self.trackTransform = ko.pureComputed(function() {
return sprintf.sprintf("rotate(%f 50 50)", self.heading());
});
function test() {
var v = [ -1, 0 ];
var phi = 0 * Math.PI/180;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
var m = [ [ cosPhi, sinPhi ], [ -sinPhi, cosPhi ] ];
var r = [ m[0][0] * v[0] + m[1][0]*v[1], m[0][1] * v[0] + m[1][1]*v[1]];
console.log(v,m,r);
}
function moveOnArc( targetHeading, r, dir ) {
dir = dir || 1;
var phi = targetHeading * Math.PI/180;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
var x = dir*r*(1- cosPhi);
var y = r * sinPhi;
return [ Number(x.toFixed(1)), Number(-y.toFixed(1)) ];
}
function moveStraight( heading, dist ) {
var phi = heading * Math.PI/180;
var cosPhi = Math.cos(phi);
var sinPhi = Math.sin(phi);
var x = dist * sinPhi;
var y = dist * cosPhi;
return [ Number(x.toFixed(1)), Number(-y.toFixed(1)) ];
}
self.trackDraw = ko.pureComputed(function() {
function entryProcedure(s, t, h) {
var d = normDeg(t - h, -180, 180);
var reply = "";
var dir = s ? 1 : -1;
if ((s && d >= -110 && d < 70) || (!s && d >= -70 && d < 110)) {
self.entry("direct");
// turn to outbound track
var turn = normDeg(dir*d+180,0,360);
var p = moveOnArc(turn, 7.5, dir );
reply += sprintf.sprintf(" a 7.5 7.5, 0, %d, %d, %f %f ", turn>180?1:0, s?1:0, p[0], p[1] );
// fly outbound
p = moveStraight(d+180, 25);
reply += sprintf.sprintf(" l %f,%f", p[0], p[1] );
// turn back to the holding pattern, intercept inbound
p = moveStraight(d-dir*90, 15 );
reply += sprintf.sprintf(" a 7.5 7.5, 0, %d, %d, %f %f ", 0, s?1:0, p[0], p[1] );
// and to the fix
reply += " L50,50";
} else if ((s && d >= -180 && d < -110) || (!s && d >= 110 && d < 180)) {
self.entry("teardrop");
// fly outbount for 1minute
var p = moveStraight(d+180-dir*30, 30 );
reply += sprintf.sprintf(" l %f,%f", p[0], p[1] );
// turn back to station
p = moveOnArc(d-dir*30, 7.5, dir );
reply += sprintf.sprintf(" a 7.5 7.5, 0, %d, %d, %f %f ", 0, s?1:0, p[0], p[1] );
reply += " L50,50";
} else if ((s && d >= 70 && d < 180) || (!s && d >= -180 && d < -70)) {
self.entry("parallel");
// turn to outbound track
var turn = normDeg(dir*d+180,0,360);
var p = moveOnArc(180-dir*d, 7.5, -dir );
reply += sprintf.sprintf(" a 7.5 7.5, 0, %d, %d, %f %f ", 0, s?0:1, p[0], p[1] );
// fly outbound
p = moveStraight(d+180, 25);
reply += sprintf.sprintf(" l %f,%f", p[0], p[1] );
// turn back to the holding pattern, intercept inbound
p = moveStraight(d+dir*90, 15 );
reply += sprintf.sprintf(" a 7.5 7.5, 0, %d, %d, %f %f ", 0, s?0:1, p[0], p[1] );
reply += " L50,50";
} else {
self.entry("unknown");
}
return reply;
}
return "M50,100 v-50 " + entryProcedure(self.standard(), self.inboundTrack(), self.heading());
});
self.setStandard = function(a, b) {
self.standard(true);
}
self.setNonStandard = function(a, b) {
self.standard(false);
}
self.inboundTrackSpin = function(event, ui) {
$(event.target).spinner("value", normDeg(ui.value, 0, 360));
return false;
}
self.headingSpin = function(event, ui) {
$(event.target).spinner("value", normDeg(ui.value, 0, 360));
return false;
}
}
// ViewModel.prototype.dispose = function() {
// }
// Return component definition
return {
viewModel : ViewModel,
template : htmlString
};
});

View file

@ -5,7 +5,7 @@ define([
function ViewModel(params) { function ViewModel(params) {
var self = this; var self = this;
self.watches = ko.observableArray([]); self.watches = ko.observableArray([0]);
self.addWatch = function() { self.addWatch = function() {
self.watches.push(self.watches().length); self.watches.push(self.watches().length);