diff --git a/webgui/topics/Tools.js b/webgui/topics/Tools.js
index 001accc1f..84bc63f93 100644
--- a/webgui/topics/Tools.js
+++ b/webgui/topics/Tools.js
@@ -2,6 +2,10 @@ define([
'knockout', 'text!./Tools.html'
], function(ko, htmlString) {
+ ko.components.register('Tools/Holding Pattern', {
+ require : 'topics/Tools/Holding'
+ });
+
ko.components.register('Tools/Stopwatch', {
require : 'topics/Tools/Stopwatch'
});
diff --git a/webgui/topics/Tools/Holding.html b/webgui/topics/Tools/Holding.html
new file mode 100644
index 000000000..87f43bbeb
--- /dev/null
+++ b/webgui/topics/Tools/Holding.html
@@ -0,0 +1,105 @@
+
+
+
+
+
diff --git a/webgui/topics/Tools/Holding.js b/webgui/topics/Tools/Holding.js
new file mode 100644
index 000000000..1220fe986
--- /dev/null
+++ b/webgui/topics/Tools/Holding.js
@@ -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
+ };
+});
diff --git a/webgui/topics/Tools/Stopwatch.js b/webgui/topics/Tools/Stopwatch.js
index 055dbdf6e..655077e3c 100644
--- a/webgui/topics/Tools/Stopwatch.js
+++ b/webgui/topics/Tools/Stopwatch.js
@@ -5,7 +5,7 @@ define([
function ViewModel(params) {
var self = this;
- self.watches = ko.observableArray([]);
+ self.watches = ko.observableArray([0]);
self.addWatch = function() {
self.watches.push(self.watches().length);