Flight-path-history.
Record the historical flight-path (by default, since last takeoff). Optionally display the flight-path in the map; other visualisations (e.g., an 'in-world' view) could also be created. Nasal API to follow so Canvas-map or FMS can show the same data. Altitude and attitude (Euler angles) are currently recorded but not used anywhere.
This commit is contained in:
parent
e62649e075
commit
433af2b51a
6 changed files with 302 additions and 1 deletions
|
@ -4,12 +4,14 @@ set(SOURCES
|
||||||
controls.cxx
|
controls.cxx
|
||||||
replay.cxx
|
replay.cxx
|
||||||
flightrecorder.cxx
|
flightrecorder.cxx
|
||||||
|
FlightHistory.cxx
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
controls.hxx
|
controls.hxx
|
||||||
replay.hxx
|
replay.hxx
|
||||||
flightrecorder.hxx
|
flightrecorder.hxx
|
||||||
|
FlightHistory.hxx
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
166
src/Aircraft/FlightHistory.cxx
Normal file
166
src/Aircraft/FlightHistory.cxx
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
// FlightHistory
|
||||||
|
//
|
||||||
|
// Written by James Turner, started December 2012.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "FlightHistory.hxx"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
|
#include <simgear/sg_inlines.h>
|
||||||
|
#include <simgear/debug/logstream.hxx>
|
||||||
|
#include <simgear/props/props_io.hxx>
|
||||||
|
#include <simgear/misc/strutils.hxx>
|
||||||
|
#include <simgear/structure/exception.hxx>
|
||||||
|
#include <simgear/math/SGMath.hxx>
|
||||||
|
|
||||||
|
#include <Main/fg_props.hxx>
|
||||||
|
#include <Main/globals.hxx>
|
||||||
|
|
||||||
|
FGFlightHistory::FGFlightHistory() :
|
||||||
|
m_sampleInterval(5.0),
|
||||||
|
m_validSampleCount(SAMPLE_BUCKET_WIDTH)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FGFlightHistory::~FGFlightHistory()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::init()
|
||||||
|
{
|
||||||
|
m_sampleInterval = fgGetDouble("/sim/history/sample-interval-sec", 1.0);
|
||||||
|
if (m_sampleInterval <= 0.0) { // would be bad
|
||||||
|
SG_LOG(SG_FLIGHT, SG_INFO, "invalid flight-history sample interval:" << m_sampleInterval
|
||||||
|
<< ", defaulting to " << m_sampleInterval);
|
||||||
|
m_sampleInterval = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_weightOnWheels = NULL;
|
||||||
|
// reset the history when we detect a take-off
|
||||||
|
if (fgGetBool("/sim/history/clear-on-takeoff", true)) {
|
||||||
|
m_weightOnWheels = fgGetNode("/gear/gear[1]/wow", 0, true);
|
||||||
|
m_lastWoW = m_weightOnWheels->getBoolValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// force bucket re-allocation
|
||||||
|
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::shutdown()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::reinit()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::update(double dt)
|
||||||
|
{
|
||||||
|
SG_UNUSED(dt); // we care about sim-time, not frame-time
|
||||||
|
|
||||||
|
if (m_weightOnWheels) {
|
||||||
|
|
||||||
|
if (m_lastWoW && !m_weightOnWheels->getBoolValue()) {
|
||||||
|
SG_LOG(SG_FLIGHT, SG_INFO, "history: detected main-gear takeoff, clearing history");
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
} // of rest-on-takeoff enabled
|
||||||
|
|
||||||
|
double elapsed = globals->get_sim_time_sec() - m_lastCaptureTime;
|
||||||
|
if (elapsed > m_sampleInterval) {
|
||||||
|
capture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::allocateNewBucket()
|
||||||
|
{
|
||||||
|
SampleBucket* bucket = new SampleBucket;
|
||||||
|
m_buckets.push_back(bucket);
|
||||||
|
m_validSampleCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::capture()
|
||||||
|
{
|
||||||
|
if (m_validSampleCount == SAMPLE_BUCKET_WIDTH) {
|
||||||
|
// bucket is full, allocate a new one
|
||||||
|
allocateNewBucket();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastCaptureTime = globals->get_sim_time_sec();
|
||||||
|
Sample* sample = m_buckets.back()->samples + m_validSampleCount;
|
||||||
|
|
||||||
|
sample->simTimeMSec = static_cast<int>(m_lastCaptureTime * 1000.0);
|
||||||
|
sample->position = globals->get_aircraft_position();
|
||||||
|
|
||||||
|
double heading, pitch, roll;
|
||||||
|
globals->get_aircraft_orientation(heading, pitch, roll);
|
||||||
|
sample->heading = static_cast<float>(heading);
|
||||||
|
sample->pitch = static_cast<float>(pitch);
|
||||||
|
sample->roll = static_cast<float>(roll);
|
||||||
|
|
||||||
|
++m_validSampleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
SGGeodVec FGFlightHistory::pathForHistory(double minEdgeLengthM) const
|
||||||
|
{
|
||||||
|
SGGeodVec result;
|
||||||
|
if (m_buckets.empty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_back(m_buckets.front()->samples[0].position);
|
||||||
|
SGVec3d lastOutputCart = SGVec3d::fromGeod(result.back());
|
||||||
|
double minLengthSqr = minEdgeLengthM * minEdgeLengthM;
|
||||||
|
|
||||||
|
BOOST_FOREACH(SampleBucket* bucket, m_buckets) {
|
||||||
|
unsigned int count = (bucket == m_buckets.back() ? m_validSampleCount : SAMPLE_BUCKET_WIDTH);
|
||||||
|
|
||||||
|
// iterate over all the valid samples in the bucket
|
||||||
|
for (unsigned int index = 0; index < count; ++index) {
|
||||||
|
SGGeod g = bucket->samples[index].position;
|
||||||
|
SGVec3d cart(SGVec3d::fromGeod(g));
|
||||||
|
if (distSqr(cart, lastOutputCart) > minLengthSqr) {
|
||||||
|
lastOutputCart = cart;
|
||||||
|
result.push_back(g);
|
||||||
|
}
|
||||||
|
} // of samples iteration
|
||||||
|
} // of buckets iteration
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGFlightHistory::clear()
|
||||||
|
{
|
||||||
|
BOOST_FOREACH(SampleBucket* ptr, m_buckets) {
|
||||||
|
delete ptr;
|
||||||
|
}
|
||||||
|
m_buckets.clear();
|
||||||
|
m_validSampleCount = SAMPLE_BUCKET_WIDTH;
|
||||||
|
}
|
||||||
|
|
105
src/Aircraft/FlightHistory.hxx
Normal file
105
src/Aircraft/FlightHistory.hxx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// FlightHistory
|
||||||
|
//
|
||||||
|
// Written by James Turner, started December 2012.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2012 James Turner - zakalawe (at) mac com
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef FG_AIRCRAFT_FLIGHT_HISTORY_HXX
|
||||||
|
#define FG_AIRCRAFT_FLIGHT_HISTORY_HXX
|
||||||
|
|
||||||
|
#include <simgear/structure/subsystem_mgr.hxx>
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
#include <simgear/math/SGMath.hxx>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory> // for std::auto_ptr
|
||||||
|
|
||||||
|
typedef std::vector<SGGeod> SGGeodVec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* record the history of the aircraft's movements, making it available
|
||||||
|
* as a contiguous block. This can be used to show the historical flight-path
|
||||||
|
* over a long period of time (unlike the replay system), but only a small,
|
||||||
|
* fixed set of properties are recorded. (Positioned and orientation, but
|
||||||
|
* not velocity, acceleration, control inputs, or so on)
|
||||||
|
*/
|
||||||
|
class FGFlightHistory : public SGSubsystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FGFlightHistory();
|
||||||
|
virtual ~FGFlightHistory();
|
||||||
|
|
||||||
|
virtual void init();
|
||||||
|
virtual void shutdown();
|
||||||
|
virtual void reinit();
|
||||||
|
virtual void update(double dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retrieve the path, collapsing segments shorter than
|
||||||
|
* the specified minimum length
|
||||||
|
*/
|
||||||
|
SGGeodVec pathForHistory(double minEdgeLengthM = 50.0) const;
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @class A single data sample in the history system.
|
||||||
|
*/
|
||||||
|
class Sample
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SGGeod position;
|
||||||
|
/// heading, pitch and roll can be recorded at lower precision
|
||||||
|
/// than a double - actually 16 bits might be sufficient
|
||||||
|
float heading, pitch, roll;
|
||||||
|
int simTimeMSec;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int SAMPLE_BUCKET_WIDTH = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bucket is a fixed-size container of samples. This is a crude slab
|
||||||
|
* allocation of samples, in chunks defined by the width constant above.
|
||||||
|
* Keep in mind that even with a 1Hz sample frequency, we use less than
|
||||||
|
* 200kbytes per hour - avoiding continous malloc traffic, or expensive
|
||||||
|
* std::vector reallocations, is the key factor here.
|
||||||
|
*/
|
||||||
|
class SampleBucket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Sample samples[SAMPLE_BUCKET_WIDTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
double m_lastCaptureTime;
|
||||||
|
double m_sampleInterval; ///< sample interval in seconds
|
||||||
|
/// our store of samples (in buckets). The last bucket is partially full,
|
||||||
|
/// with the number of valid samples indicated by m_validSampleCount
|
||||||
|
std::vector<SampleBucket*> m_buckets;
|
||||||
|
|
||||||
|
/// number of valid samples in the final bucket
|
||||||
|
unsigned int m_validSampleCount;
|
||||||
|
|
||||||
|
SGPropertyNode_ptr m_weightOnWheels;
|
||||||
|
bool m_lastWoW;
|
||||||
|
|
||||||
|
void allocateNewBucket();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void capture();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -25,6 +25,7 @@
|
||||||
#include <Airports/runways.hxx>
|
#include <Airports/runways.hxx>
|
||||||
#include <Main/fg_os.hxx> // fgGetKeyModifiers()
|
#include <Main/fg_os.hxx> // fgGetKeyModifiers()
|
||||||
#include <Navaids/routePath.hxx>
|
#include <Navaids/routePath.hxx>
|
||||||
|
#include <Aircraft/FlightHistory.hxx>
|
||||||
|
|
||||||
const char* RULER_LEGEND_KEY = "ruler-legend";
|
const char* RULER_LEGEND_KEY = "ruler-legend";
|
||||||
|
|
||||||
|
@ -411,6 +412,7 @@ void MapWidget::setProperty(SGPropertyNode_ptr prop)
|
||||||
_root->setIntValue("max-zoom", MAX_ZOOM);
|
_root->setIntValue("max-zoom", MAX_ZOOM);
|
||||||
_root->setBoolValue("centre-on-aircraft", true);
|
_root->setBoolValue("centre-on-aircraft", true);
|
||||||
_root->setBoolValue("draw-data", false);
|
_root->setBoolValue("draw-data", false);
|
||||||
|
_root->setBoolValue("draw-flight-history", false);
|
||||||
_root->setBoolValue("magnetic-headings", true);
|
_root->setBoolValue("magnetic-headings", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,6 +613,7 @@ void MapWidget::draw(int dx, int dy)
|
||||||
drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
|
drawNavRadio(fgGetNode("/instrumentation/nav[0]", false));
|
||||||
drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
|
drawNavRadio(fgGetNode("/instrumentation/nav[1]", false));
|
||||||
paintAircraftLocation(_aircraft);
|
paintAircraftLocation(_aircraft);
|
||||||
|
drawFlightHistory();
|
||||||
paintRoute();
|
paintRoute();
|
||||||
paintRuler();
|
paintRuler();
|
||||||
|
|
||||||
|
@ -748,6 +751,28 @@ void MapWidget::paintRoute()
|
||||||
} // of second waypoint iteration
|
} // of second waypoint iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MapWidget::drawFlightHistory()
|
||||||
|
{
|
||||||
|
FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history");
|
||||||
|
if (!history || !_root->getBoolValue("draw-flight-history")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first pass, draw the actual lines
|
||||||
|
glLineWidth(2.0);
|
||||||
|
|
||||||
|
SGGeodVec gv(history->pathForHistory());
|
||||||
|
glColor4f(0.0, 0.0, 1.0, 0.7);
|
||||||
|
|
||||||
|
glBegin(GL_LINE_STRIP);
|
||||||
|
for (unsigned int i=0; i<gv.size(); ++i) {
|
||||||
|
SGVec2d p = project(gv[i]);
|
||||||
|
glVertex2d(p.x(), p.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Round a SGGeod to an arbitrary precision.
|
* Round a SGGeod to an arbitrary precision.
|
||||||
* For example, passing precision of 0.5 will round to the nearest 0.5 of
|
* For example, passing precision of 0.5 will round to the nearest 0.5 of
|
||||||
|
|
|
@ -43,6 +43,7 @@ private:
|
||||||
void paintAircraftLocation(const SGGeod& aircraftPos);
|
void paintAircraftLocation(const SGGeod& aircraftPos);
|
||||||
void paintRoute();
|
void paintRoute();
|
||||||
void paintRuler();
|
void paintRuler();
|
||||||
|
void drawFlightHistory();
|
||||||
|
|
||||||
void drawGPSData();
|
void drawGPSData();
|
||||||
void drawNavRadio(SGPropertyNode_ptr radio);
|
void drawNavRadio(SGPropertyNode_ptr radio);
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
#include <Aircraft/controls.hxx>
|
#include <Aircraft/controls.hxx>
|
||||||
#include <Aircraft/replay.hxx>
|
#include <Aircraft/replay.hxx>
|
||||||
|
#include <Aircraft/FlightHistory.hxx>
|
||||||
#include <Airports/runways.hxx>
|
#include <Airports/runways.hxx>
|
||||||
#include <Airports/simple.hxx>
|
#include <Airports/simple.hxx>
|
||||||
#include <Airports/dynamics.hxx>
|
#include <Airports/dynamics.hxx>
|
||||||
|
@ -711,7 +712,8 @@ void fgCreateSubsystems() {
|
||||||
// Initialize the replay subsystem
|
// Initialize the replay subsystem
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
globals->add_subsystem("replay", new FGReplay);
|
globals->add_subsystem("replay", new FGReplay);
|
||||||
|
globals->add_subsystem("history", new FGFlightHistory);
|
||||||
|
|
||||||
#ifdef ENABLE_AUDIO_SUPPORT
|
#ifdef ENABLE_AUDIO_SUPPORT
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Initialize the sound-effects subsystem.
|
// Initialize the sound-effects subsystem.
|
||||||
|
|
Loading…
Add table
Reference in a new issue