new gps / ahrs page with both data details and graphical representation using image for gps location and webgl for ahrs

pull/81/head
bradanlane 2015-10-14 17:35:41 -04:00
rodzic 8769ced73e
commit 6e84557616
17 zmienionych plików z 3852 dodań i 58 usunięć

Wyświetl plik

@ -15,10 +15,13 @@ www:
mkdir -p /var/www/css
cp web/css/*.css /var/www/css
mkdir -p /var/www/js
cp web/js/*.js /var/www/js
cp web/js/main.js /var/www/js
cp web/js/addtohomescreen.min.js /var/www/js
cp web/js/j3di-all.min.js /var/www/js
mkdir -p /var/www/img
cp web/img/logo*.png /var/www/img
cp web/img/screen*.png /var/www/img
cp web/img/world.png /var/www/img
mkdir -p /var/www/maui
mkdir -p /var/www/maui/js
cp web/maui/js/angular-ui-router.min.js /var/www/maui/js

Wyświetl plik

@ -6,16 +6,48 @@
.weather-page {}
.gps_page {}
.map-container {
position: relative;
}
.world-map {
background-image: url(../img/world.png);
background-position: 1186px 591px;
margin-bottom: 4px;
/* this will be set dynamically to center map at gps location */
/*
width: 100%;
height: 300px;
*/
}
.mark-position {
position: absolute;
top: 0;
left: 0;
font-size: 18px;
}
.washout {
background-color: rgba(255, 255, 255, 0.65);
display: inline-block;
width: 100%;
text-align: center;
padding: 4px;
}
.section_invisible {
display: none;
}
.text-normal {
font-weight:100;
font-weight: 100;
}
.reset-flow {
clear:both;
clear: both;
}
.separator {
@ -86,20 +118,23 @@
}
.flight_condition_VFR {
background-color:forestgreen;
color:white;
background-color: forestgreen;
color: white;
}
.flight_condition_MVFR {
background-color:blue;
color:white;
background-color: blue;
color: white;
}
.flight_condition_IFR {
background-color:crimson;
color:white;
background-color: crimson;
color: white;
}
.flight_condition_LIFR {
background-color:darkorchid;
color:white;
background-color: darkorchid;
color: white;
}
.traffic-style1 {
@ -129,16 +164,18 @@
}
.bar_container {
display:inline-block;
border:1px solid #cccccc;
display: inline-block;
border: 1px solid #cccccc;
width: 100%;
border-radius: 2px;
font-size: 0.75em;
border-radius: 2px;
font-size: 0.75em;
}
.bar_display {
padding: 1px 2px 1px 3px;
}
/* ***************************************************************************
everything below this comment represents tweeks to the mobile-angular-uis CSS
*************************************************************************** */

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 58 KiB

BIN
web/img/world.png 100755

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 117 KiB

Wyświetl plik

@ -39,9 +39,7 @@
<link rel="stylesheet" type="text/css" href="css/addtohomescreen.css">
<script src="js/addtohomescreen.min.js"></script>
<script>
addToHomescreen({
displayPace: 480
});
addToHomescreen({ displayPace: 480 });
</script>
<link rel="stylesheet" href="maui/css/mobile-angular-ui-base.min.css" />
@ -57,12 +55,14 @@
<!-- TODO: combine and minify the following javascript -->
<script src="js/main.js"></script>
<script src="plates/js/gps.js"></script>
<script src="plates/js/logs.js"></script>
<script src="plates/js/settings.js"></script>
<script src="plates/js/status.js"></script>
<script src="plates/js/traffic.js"></script>
<script src="plates/js/weather.js"></script>
<script src="js/j3di-all.min.js"></script>
<script src="plates/js/ahrs.js"></script>
<script src="plates/js/gps.js"></script>
</head>
<body ng-app="stratux" ng-controller="MainCtrl" ui-prevent-touchmove-defaults>

3344
web/js/j3di-all.js 100755

Plik diff jest za duży Load Diff

2
web/js/j3di-all.min.js vendored 100755

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1,9 +1,11 @@
// application constants
var URL_HOST_BASE = window.location.hostname;
var URL_SETTINGS_GET = "http://" + URL_HOST_BASE + "/getSettings";
var URL_SETTINGS_SET = "http://" + URL_HOST_BASE + "/setSettings";
var URL_HOST_BASE = window.location.hostname;
var URL_SETTINGS_GET = "http://" + URL_HOST_BASE + "/getSettings";
var URL_SETTINGS_SET = "http://" + URL_HOST_BASE + "/setSettings";
var URL_GPS_GET = "http://" + URL_HOST_BASE + "/getSituation";
var URL_STATUS_WS = "ws://" + URL_HOST_BASE + "/status"
var URL_TRAFFIC_WS = "ws://" + URL_HOST_BASE + "/traffic";
var URL_WEATHER_WS = "ws://" + URL_HOST_BASE + "/weather";
// define the module with dependency on mobile-angular-ui
//var app = angular.module('stratux', ['ngRoute', 'mobile-angular-ui', 'mobile-angular-ui.gestures', 'appControllers']);
@ -13,12 +15,42 @@ var appControllers = angular.module('appControllers', []);
app.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', { url: '/', templateUrl: 'plates/status.html', controller: 'StatusCtrl', reloadOnSearch: false })
.state('weather', { url: '/weather', templateUrl: 'plates/weather.html', controller: 'WeatherCtrl', reloadOnSearch: false })
.state('traffic', { url: '/traffic', templateUrl: 'plates/traffic.html', controller: 'TrafficCtrl', reloadOnSearch: false })
.state('gps', { url: '/gps', templateUrl: 'plates/gps.html', controller: 'GPSCtrl', reloadOnSearch: false })
.state('logs', { url: '/logs', templateUrl: 'plates/logs.html', controller: 'LogsCtrl', reloadOnSearch: false })
.state('settings', { url: '/settings', templateUrl: 'plates/settings.html',controller: 'SettingsCtrl', reloadOnSearch: false });
.state('home', {
url: '/',
templateUrl: 'plates/status.html',
controller: 'StatusCtrl',
reloadOnSearch: false
})
.state('weather', {
url: '/weather',
templateUrl: 'plates/weather.html',
controller: 'WeatherCtrl',
reloadOnSearch: false
})
.state('traffic', {
url: '/traffic',
templateUrl: 'plates/traffic.html',
controller: 'TrafficCtrl',
reloadOnSearch: false
})
.state('gps', {
url: '/gps',
templateUrl: 'plates/gps.html',
controller: 'GPSCtrl',
reloadOnSearch: false
})
.state('logs', {
url: '/logs',
templateUrl: 'plates/logs.html',
controller: 'LogsCtrl',
reloadOnSearch: false
})
.state('settings', {
url: '/settings',
templateUrl: 'plates/settings.html',
controller: 'SettingsCtrl',
reloadOnSearch: false
});
$urlRouterProvider.otherwise('/');
});

Wyświetl plik

@ -0,0 +1,6 @@
<div class="section text-left help-page">
<p>The <strong>GPS / AHRS</strong> page provides a view on the current status of GPS data and AHRS orientation. The Satellite count is located on the <strong>Status</strong> page.</p>
<p>The <strong>GPS</strong> reports position with estimated accuracy, ground track, ground speed, and GPS derived altitude are displayed along with the location depicted on a world map.</p>
<p>The <strong>AHRS</strong> reports magnetic heading, pressure altitude, pitch and roll are displayed along with a graphical representation of movement.</p>
<p class="text-warning">NOTE: This page is for reference only and must not be used for flight operations.</p>
</div>

Wyświetl plik

@ -1,13 +1,66 @@
<div class="list-group text-center">
<div class="list-group-item list-group-item-home">
<h2>GPS Status</h2>
</div>
<div class="list-group-item list-group-item-home">
<div>
<i class="fa fa-globe feature-icon text-primary"></i>
</div>
<div>
this space reserved to display GPS/AHRS status
</div>
</div>
</div>
<div class="col-sm-12">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading"><span class="panel_label">GPS</span></div>
<div class="panel-body">
<div class="row">
<span class="col-xs-12">
<div class="map-container">
<div id="map_display" class="world-map" ng-attr-style="background-position:{{map_pos_x + 'px ' + map_pos_y + 'px'}}; width:{{map_width}}px; height:{{map_height}}px;">
<div class="mark-position" ng-style="{left: map_mark_x+'px', top: map_mark_y+'px'}">
<span class="fa fa-crosshairs icon-red"></span>
</div>
</div>
</div>
</span>
</div>
<div class="separator"></div>
<div class="row">
<strong class="col-xs-6 text-center">Location:</strong>
<strong class="col-xs-6 text-center">Track:</strong>
</div>
<div class="row">
<span class="col-xs-6 text-center">{{gps_lat}}, {{gps_lon}} &plusmn; {{gps_accuracy}} m</span>
<span class="col-xs-6 text-center">{{gps_track}}&deg; @ {{gps_speed}}KTS @ {{gps_alt}} ft</span>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading"><span class="panel_label">AHRS</span></div>
<div class="panel-body">
<div class="row">
<span class="col-xs-12">
<canvas id="ahrs_display">
your web browser doesn't support the &lt;canvas>&gt; element.
</canvas>
</span>
</div>
<div class="separator"></div>
<div class="row">
<strong class="col-xs-3 text-center">Heading:</strong>
<strong class="col-xs-3 text-center">Pitch:</strong>
<strong class="col-xs-3 text-center">Roll:</strong>
<strong class="col-xs-3 text-center">P-Alt:</strong>
</div>
<div class="row">
<span class="col-xs-3 text-center">{{ahrs_heading}}&deg;</span>
<span class="col-xs-3 text-center">{{ahrs_pitch}}&deg;</span>
<span class="col-xs-3 text-center">{{ahrs_roll}}&deg;</span>
<span class="col-xs-3 text-center">{{ahrs_alt}}&deg;</span>
</div>
</div>
</div>
</div>
</div>
<!--
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Raw GPS / AHRS Data</div>
<div class="panel-body">
<pre>{{raw_data}}</pre>
</div>
</div>
</div>
-->

Wyświetl plik

@ -0,0 +1,207 @@
function ahrsRenderer(location_id) {
this.gcontext = {}; // globals
this.gl = null;
this.width = -1;
this.height = -1;
this.locationID = location_id;
this.canvas = document.getElementById(location_id);
this.canvas_container = document.getElementById(location_id).parentElement;
}
ahrsRenderer.prototype = {
constructor: ahrsRenderer,
_init_canvas: function () {
var gl = initWebGL(this.locationID);
if (!gl)
return;
this.gl = gl;
vertex_shader = 'uniform mat4 u_modelViewProjMatrix; uniform mat4 u_normalMatrix; uniform vec3 lightDir; attribute vec3 vNormal; attribute vec4 vColor; attribute vec4 vPosition; varying float v_Dot; varying vec4 v_Color; void main() { gl_Position = u_modelViewProjMatrix * vPosition; v_Color = vColor; vec4 transNormal = u_normalMatrix * vec4(vNormal, 1); v_Dot = max(dot(transNormal.xyz, lightDir), 0.0); }';
fragment_shader = 'precision mediump float; varying float v_Dot; varying vec4 v_Color; void main() { gl_FragColor = vec4(v_Color.xyz * v_Dot, v_Color.a * 0.95); }';
var vertexShader = loadShaderVertexScript(gl, vertex_shader);
var fragmentShader = loadShaderFragmentScript(gl, fragment_shader);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Bind attributes
gl.bindAttribLocation(program, 0, "vNormal");
gl.bindAttribLocation(program, 1, "vColor");
gl.bindAttribLocation(program, 2, "vPosition");
gl.linkProgram(program);
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked && !gl.isContextLost()) {
// something went wrong with the link
var error = gl.getProgramInfoLog(program);
log("Error in program linking:" + error);
gl.deleteProgram(program);
gl.deleteProgram(fragmentShader);
gl.deleteProgram(vertexShader);
} else {
gl.useProgram(program);
gl.clearColor(0, 0, 0, 0) // rgba for background color
gl.clearDepth(10000); //??
if (false /* funcky blending */ ) {
gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.depthFunc(gl.LESS);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
} else {
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}
this.gcontext.program = program;
}
},
init: function () {
this._init_canvas(); // init the canvas
g = this.gcontext;
gl = this.gl;
if (gl === null)
return;
// Set up a uniform variable for the shaders
gl.uniform3f(gl.getUniformLocation(g.program, "lightDir"), 0, 1, -1);
// Create a box. On return 'gl' contains a 'box' property with
// the BufferObjects containing the arrays for vertices,
// normals, texture coords, and indices.
// g.box = makeBox(gl);
g.box = makePaperAirplane(gl);
// Create some matrices to use later and save their locations in the shaders
g.mvMatrix = new J3DIMatrix4();
g.u_normalMatrixLoc = gl.getUniformLocation(g.program, "u_normalMatrix");
g.normalMatrix = new J3DIMatrix4();
g.u_modelViewProjMatrixLoc =
gl.getUniformLocation(g.program, "u_modelViewProjMatrix");
g.mvpMatrix = new J3DIMatrix4();
// Enable all of the vertex attribute arrays.
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);
// Set up all the vertex attributes for vertices, normals and colors
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.vertexObject);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.normalObject);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.colorObject);
gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, false, 0, 0);
// Bind the index array
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g.box.indexObject);
this.pitch = 0;
this.roll = 0;
this.heading = 0;
this.resize();
},
resize: function () {
gl = this.gl;
g = this.gcontext;
if (gl === null)
return;
var canvasWidth = this.canvas_container.offsetWidth - 12; // was (2*(this.canvas_container.offsetLeft)) // account for padding adjustments
if (canvasWidth !== this.width) {
this.width = canvasWidth;
this.height = canvasWidth *0.5;
this.canvas.width = this.width;
this.canvas.height = this.height;
// Set the viewport and projection matrix for the scene
gl.viewport(0, 0, this.width, this.height);
g.perspectiveMatrix = new J3DIMatrix4();
g.perspectiveMatrix.perspective(30, this.width / this.height, 1, 10000);
g.perspectiveMatrix.lookat( 0, 0, 4, // eye location
0, 0, 0, // focal point
0, 1, 0); // up vector
}
},
orientation: function (x, y, z) {
if (x > 360) x -= 360;
if (y > 360) y -= 360;
if (z > 360) z -= 360;
this.pitch = x; // need to reorient to level
this.roll = y;
this.heading = z;
},
animate: function (t, x, y, z) {
var FPS = 24; // we assume we can maintain a certain frame rate
var x_inc = ((x - this.pitch) / (FPS * t));
var y_inc = ((y - this.roll) / (FPS * t));
// let the animation wrap aroung gracefully
if ((z < this.heading) && (this.heading - z) > 180)
z += 360;
var z_inc = ((z - this.heading) / (FPS * t));
var _this = this;
//console.log(z_inc);
var frames = 0;
var f = function () {
_this.pitch += x_inc;
_this.roll += y_inc;
_this.heading += z_inc;
if (frames < (FPS * t)) {
_this.draw();
frames++
window.requestAnimationFrame(f, _this.canvas); // recurse
} else {
_this.orientation(x, y, z);
}
};
f();
},
draw: function () {
this.resize();
gl = this.gl;
g = this.gcontext;
if (gl === null)
return;
// Clear the canvas
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Make a model/view matrix.
g.mvMatrix.makeIdentity();
g.mvMatrix.rotate(15, 1, 0, 0); // adjust viewing angle slightly by pitching the airplane up
g.mvMatrix.rotate(-this.pitch, 1, 0, 0);
g.mvMatrix.rotate(-this.roll, 0, 0, 1);
g.mvMatrix.rotate(-this.heading, 0, 1, 0);
// Construct the normal matrix from the model-view matrix and pass it in
g.normalMatrix.load(g.mvMatrix);
g.normalMatrix.invert();
g.normalMatrix.transpose();
g.normalMatrix.setUniform(gl, g.u_normalMatrixLoc, false);
// Construct the model-view * projection matrix and pass it in
g.mvpMatrix.load(g.perspectiveMatrix);
g.mvpMatrix.multiply(g.mvMatrix);
g.mvpMatrix.setUniform(gl, g.u_modelViewProjMatrixLoc, false);
// Draw the object
gl.drawElements(gl.TRIANGLES, g.box.numIndices, gl.UNSIGNED_BYTE, 0);
}
}

Wyświetl plik

@ -1,15 +1,125 @@
angular.module('appControllers').controller('GPSCtrl', GPSCtrl); // get the main module contollers set
GPSCtrl.$inject = ['$rootScope', '$scope', '$state', '$http']; // Inject my dependencies
GPSCtrl.$inject = ['$rootScope', '$scope', '$state', '$http', '$interval']; // Inject my dependencies
// create our controller function with all necessary logic
function GPSCtrl($rootScope, $scope, $state, $http) {
function GPSCtrl($rootScope, $scope, $state, $http, $interval) {
$scope.$parent.helppage = 'plates/gps-help.html';
/*
$state.get('weather').onEnter = function () {
};
$state.get('weather').onExit = function () {
};
*/
var status = {};
var display_area_size = -1;
function sizeMap() {
var width = 0;
var el = document.getElementById("map_display").parentElement;
width = el.offsetWidth; // was (- (2 * el.offsetLeft))
if (width !== display_area_size) {
display_area_size = width;
$scope.map_width = width;
$scope.map_height = width *0.5;
}
return width;
}
function setGeoReferenceMap(la, lo) {
// Mercator projection
// var map = "img/world.png";
var map_width = 3929;
var map_height = 2184;
var map_zero_x = 1868;
var map_zero_y = 1412;
var font_size = 18; // size of font used for marker
sizeMap();
var div_width = $scope.map_width;
var div_height = $scope.map_height;
// longitude: just scale and shift
var x = (map_width * (180 + lo) / 360) - (map_width/2 - map_zero_x); // longitude_shift;
// latitude: using the Mercator projection
la_rad = la * Math.PI / 180; // convert from degrees to radians
merc_n = Math.log(Math.tan((la_rad / 2) + (Math.PI / 4))); // do the Mercator projection (w/ equator of 2pi units)
var y = (map_height / 2) - (map_width * merc_n / (2 * Math.PI)) - (map_height/2 - map_zero_y); // fit it to our map
// dot = '<div style="position:absolute; width:' + dot_size + 'px; height:' + dot_size + 'px; top:' + y + 'px; left:' + x + 'px; background:#ff7f00;"></div>';
// <img src="map-world-medium.png" style="position:absolute;top:0px;left:0px">
$scope.map_pos_x = map_width - Math.round(x - (div_width / 2));
$scope.map_pos_y = map_height - Math.round(y - (div_height / 2));
$scope.map_mark_x = Math.round((div_width - (font_size * 0.85)) / 2);
$scope.map_mark_y = Math.round((div_height - font_size) / 2);
};
function loadStatus(data) {
status = angular.fromJson(data);
// consider using angular.extend()
$scope.raw_data = angular.toJson(data, true); // makes it pretty
/* not currently used
$scope.gps_satellites = status.Satellites;
*/
$scope.gps_accuracy = Math.round(status.Accuracy);
// NACp should be an integer value in the range of 0 .. 11
// var accuracies = ["≥ 10 NM", "< 10 NM", "< 4 NM", "< 2 NM", "< 1 NM", "< 0.5 NM", "< 0.3 NM", "< 0.1 NM", "< 100 m", "< 30 m", "< 10 m", "< 3 m"];
// $scope.gps_accuracy = accuracies[status.NACp];
// "LastFixLocalTime":"2015-10-11T16:47:03.523085162Z"
$scope.gps_lat = status.Lat.toPrecision(6); // result is string
$scope.gps_lon = status.Lng.toPrecision(6); // result is string
$scope.gps_alt = Math.round(status.Alt);
$scope.gps_track = status.TrueCourse;
$scope.gps_speed = status.GroundSpeed;
// "LastGroundTrackTime":"0001-01-01T00:00:00Z"
/* not currently used
$scope.ahrs_temp = status.Temp;
*/
$scope.ahrs_alt = Math.round(status.Pressure_alt);
$scope.ahrs_heading = Math.round(status.Gyro_heading);
// pitch and roll are in degrees
$scope.ahrs_pitch = Math.round(status.Pitch);
$scope.ahrs_roll = Math.round(status.Roll);
// "LastAttitudeTime":"2015-10-11T16:47:03.534615187Z"
setGeoReferenceMap(status.Lat, status.Lng);
// $scope.$apply();
};
function getStatus() {
// Simple GET request example (note: responce is asynchronous)
$http.get(URL_GPS_GET).
then(function (response) {
loadStatus(response.data);
ahrs.animate(1, $scope.ahrs_pitch, $scope.ahrs_roll, $scope.ahrs_heading);
// $scope.$apply();
}, function (response) {
$scope.raw_data = "error getting gps / ahrs status";
});
};
var updateStatus = $interval(function () {
// refresh GPS/AHRS status once each second (aka polling)
getStatus();
}, (1 * 1000), 0, false);
$state.get('gps').onEnter = function () {
// everything gets handled correctly by the controller
};
$state.get('gps').onExit = function () {
// stop polling for gps/ahrs status
$interval.cancel(updateStatus);
};
// GPS/AHRS Controller tasks go here
var ahrs = new ahrsRenderer("ahrs_display");
ahrs.init();
ahrs.orientation(0, 0, 0);
// Weather Controller tasks go here
};

Wyświetl plik

@ -11,7 +11,7 @@ function StatusCtrl($rootScope, $scope, $state, $http) {
return; // we are getting called once after clicking away from the status page
if (($scope.socket === undefined) || ($scope.socket === null)) {
socket = new WebSocket('ws://' + URL_HOST_BASE + '/status');
socket = new WebSocket(URL_STATUS_WS);
$scope.socket = socket; // store socket in scope for enter/exit usage
}

Wyświetl plik

@ -52,7 +52,7 @@ function TrafficCtrl($rootScope, $scope, $state, $http, $interval) {
return; // we are getting called once after clicking away from the status page
if (($scope.socket === undefined) || ($scope.socket === null)) {
socket = new WebSocket('ws://' + URL_HOST_BASE + '/traffic');
socket = new WebSocket(URL_TRAFFIC_WS);
$scope.socket = socket; // store socket in scope for enter/exit usage
}

Wyświetl plik

@ -137,7 +137,7 @@ function WeatherCtrl($rootScope, $scope, $state, $http, $interval) {
return; // we are getting called once after clicking away from the status page
if (($scope.socket === undefined) || ($scope.socket === null)) {
socket = new WebSocket('ws://' + URL_HOST_BASE + '/weather');
socket = new WebSocket(URL_WEATHER_WS);
$scope.socket = socket; // store socket in scope for enter/exit usage
}

Wyświetl plik

@ -83,7 +83,7 @@
</div>
<!--
ssh<div class="col-sm-12">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">Raw Configuration</div>
<div class="panel-body">

Wyświetl plik

@ -29,13 +29,13 @@
<div class="row" ng-class="{'section_invisible': !visible_uat}">
<span class="col-xs-1"></span>
<label class="col-xs-3">UAT:</label>
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style2" ng-attr-style="width:{{100*UAT_messages_last_minute / UAT_messages_max}}%">{{UAT_messages_last_minute}}</div></div></span>
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style2" ng-attr-style="width:{{UAT_messages_max ? 100*UAT_messages_last_minute / UAT_messages_max : 0}}%">{{UAT_messages_last_minute}}</div></div></span>
<span class="col-xs-2 text-right">{{UAT_messages_max}}</span>
</div>
<div class="row" ng-class="{'section_invisible': !visible_es}">
<span class="col-xs-1"></span>
<label class="col-xs-3">1090ES:</label>
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style1" ng-attr-style="width:{{100*ES_messages_last_minute / ES_messages_max}}%;">{{ES_messages_last_minute}}</div></div></span>
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style1" ng-attr-style="width:{{ES_messages_max ? 100*ES_messages_last_minute / ES_messages_max : 0}}%;">{{ES_messages_last_minute}}</div></div></span>
<span class="col-xs-2 text-right">{{ES_messages_max}}</span>
</div>
<!--