From 2af78ec1686fc057a56f3cc70522c34fd983c41e Mon Sep 17 00:00:00 2001 From: daveluff Date: Wed, 30 Nov 2005 00:21:25 +0000 Subject: [PATCH] kln89 GPS unit simulation --- src/Instrumentation/KLN89/Makefile.am | 21 + src/Instrumentation/KLN89/kln89.cxx | 1404 ++++++++++++++++++++++ src/Instrumentation/KLN89/kln89.hxx | 269 +++++ src/Instrumentation/KLN89/kln89_page.cxx | 204 ++++ src/Instrumentation/KLN89/kln89_page.hxx | 93 ++ 5 files changed, 1991 insertions(+) create mode 100644 src/Instrumentation/KLN89/Makefile.am create mode 100644 src/Instrumentation/KLN89/kln89.cxx create mode 100644 src/Instrumentation/KLN89/kln89.hxx create mode 100644 src/Instrumentation/KLN89/kln89_page.cxx create mode 100644 src/Instrumentation/KLN89/kln89_page.hxx diff --git a/src/Instrumentation/KLN89/Makefile.am b/src/Instrumentation/KLN89/Makefile.am new file mode 100644 index 000000000..bbd5e2199 --- /dev/null +++ b/src/Instrumentation/KLN89/Makefile.am @@ -0,0 +1,21 @@ +noinst_LIBRARIES = libKLN89.a + +libKLN89_a_SOURCES = \ + kln89.cxx kln89.hxx \ + kln89_page.cxx kln89_page.hxx \ + kln89_page_act.cxx kln89_page_act.hxx \ + kln89_page_apt.cxx kln89_page_apt.hxx \ + kln89_page_cal.cxx kln89_page_cal.hxx \ + kln89_page_dir.cxx kln89_page_dir.hxx \ + kln89_page_fpl.cxx kln89_page_fpl.hxx \ + kln89_page_int.cxx kln89_page_int.hxx \ + kln89_page_nav.cxx kln89_page_nav.hxx \ + kln89_page_ndb.cxx kln89_page_ndb.hxx \ + kln89_page_nrst.cxx kln89_page_nrst.hxx \ + kln89_page_oth.cxx kln89_page_oth.hxx \ + kln89_page_set.cxx kln89_page_set.hxx \ + kln89_page_usr.cxx kln89_page_usr.hxx \ + kln89_page_vor.cxx kln89_page_vor.hxx \ + kln89_symbols.hxx + +INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src \ No newline at end of file diff --git a/src/Instrumentation/KLN89/kln89.cxx b/src/Instrumentation/KLN89/kln89.cxx new file mode 100644 index 000000000..e66115bcb --- /dev/null +++ b/src/Instrumentation/KLN89/kln89.cxx @@ -0,0 +1,1404 @@ +// kln89_page.cxx - a class to manage the simulation of a KLN89 +// GPS unit. Note that this is primarily the +// simulation of the user interface and display +// - the core GPS calculations such as position +// and waypoint sequencing are done (or should +// be done) by FG code. +// +// Written by David Luff, started 2005. +// +// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + +#include "kln89.hxx" +#include "kln89_page.hxx" +#include "kln89_page_apt.hxx" +#include "kln89_page_vor.hxx" +#include "kln89_page_ndb.hxx" +#include "kln89_page_int.hxx" +#include "kln89_page_usr.hxx" +#include "kln89_page_act.hxx" +#include "kln89_page_nav.hxx" +#include "kln89_page_fpl.hxx" +#include "kln89_page_cal.hxx" +#include "kln89_page_set.hxx" +#include "kln89_page_oth.hxx" +#include "kln89_page_dir.hxx" +#include "kln89_page_nrst.hxx" +#include "kln89_symbols.hxx" +#include + +#include +#include + +SG_USING_STD(cout); + +KLN89::KLN89(RenderArea2D* instrument) +: DCLGPS(instrument) { + _mode = KLN89_MODE_DISP; + _blink = false; + _cum_dt = 0.0; + _nFields = 2; + _maxFields = 2; + _xBorder = 0; + _yBorder = 4; + // ..Field..[0] => no fields in action + _xFieldBorder[0] = 0; + _xFieldBorder[1] = 0; + _yFieldBorder[0] = 0; + _yFieldBorder[1] = 0; + _xFieldBorder[2] = 2; + _yFieldBorder[2] = 0; + _xFieldStart[0] = 0; + _xFieldStart[1] = 0; + _xFieldStart[2] = 45; + _yFieldStart[0] = 0; + _yFieldStart[1] = 0; + _yFieldStart[2] = 0; + + //_pixelated = true; + _pixelated = false; + + // Cyclic pages + GPSPage* apt_page = new KLN89AptPage(this); + _pages.push_back(apt_page); + GPSPage* vor_page = new KLN89VorPage(this); + _pages.push_back(vor_page); + GPSPage* ndb_page = new KLN89NDBPage(this); + _pages.push_back(ndb_page); + GPSPage* int_page = new KLN89IntPage(this); + _pages.push_back(int_page); + GPSPage* usr_page = new KLN89UsrPage(this); + _pages.push_back(usr_page); + GPSPage* act_page = new KLN89ActPage(this); + _pages.push_back(act_page); + GPSPage* nav_page = new KLN89NavPage(this); + _pages.push_back(nav_page); + GPSPage* fpl_page = new KLN89FplPage(this); + _pages.push_back(fpl_page); + GPSPage* cal_page = new KLN89CalPage(this); + _pages.push_back(cal_page); + GPSPage* set_page = new KLN89SetPage(this); + _pages.push_back(set_page); + GPSPage* oth_page = new KLN89OthPage(this); + _pages.push_back(oth_page); + _nPages = _pages.size(); + _curPage = 0; + + // Other pages + _dir_page = new KLN89DirPage(this); + _nrst_page = new KLN89NrstPage(this); + + _activePage = apt_page; + _obsMode = false; + _dto = false; + _fullLegMode = true; + _obsHeading = 215; + + _maxFlightPlans = 26; + for(unsigned int i=0; i<_maxFlightPlans; ++i) { + GPSFlightPlan* fp = new GPSFlightPlan; + fp->waypoints.clear(); + _flightPlans.push_back(fp); + } + _activeFP = _flightPlans[0]; + + // Hackish + _entJump = -1; + _entRestoreCrsr = false; + + _dispMsg = false; + + // Moving map stuff + _mapOrientation = 0; + _mapHeading = 0.0; + _mapHeadingUpdateTimer = 0.0; + _drawSUA = false; + _drawVOR = false; + _drawApt = true; + //_mapScaleIndex = 20; + _mapScaleIndex = 7; // I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing! + _mapScaleAuto = true; + + // Mega-hack - hardwire airport town and state names for the FG base area since we don't have any data for these at the moment + // TODO - do this better one day! + _airportTowns["KSFO"] = "San Francisco"; + _airportTowns["KSQL"] = "San Carlos"; + _airportTowns["KPAO"] = "Palo Alto"; + _airportTowns["KNUQ"] = "Mountain View"; + _airportTowns["KSJC"] = "San Jose"; + _airportTowns["KRHV"] = "San Jose"; + _airportTowns["E16"] = "San Martin"; + _airportTowns["KWVI"] = "Watsonville"; + _airportTowns["KOAK"] = "Oakland"; + _airportTowns["KHWD"] = "Hayward"; + _airportTowns["KLVK"] = "Livermore"; + _airportTowns["KCCR"] = "Concord"; + _airportTowns["KTCY"] = "Tracy"; + _airportTowns["KSCK"] = "Stockton"; + _airportTowns["KHAF"] = "Half Moon Bay"; + + _airportStates["KSFO"] = "CA"; + _airportStates["KSQL"] = "CA"; + _airportStates["KPAO"] = "CA"; + _airportStates["KNUQ"] = "CA"; + _airportStates["KSJC"] = "CA"; + _airportStates["KRHV"] = "CA"; + _airportStates["E16"] = "CA"; + _airportStates["KWVI"] = "CA"; + _airportStates["KOAK"] = "CA"; + _airportStates["KHWD"] = "CA"; + _airportStates["KLVK"] = "CA"; + _airportStates["KCCR"] = "CA"; + _airportStates["KTCY"] = "CA"; + _airportStates["KSCK"] = "CA"; + _airportStates["KHAF"] = "CA"; +} + +KLN89::~KLN89() { + for(unsigned int i=0; i<_pages.size(); ++i) { + delete _pages[i]; + } + + delete _dir_page; + + for(unsigned int i=0; i<_maxFlightPlans; ++i) { + ClearFlightPlan(i); + delete _flightPlans[i]; + } +} + +void KLN89::bind() { + fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert); + DCLGPS::bind(); +} + +void KLN89::unbind() { + fgUntie("/instrumentation/gps/message-alert"); + DCLGPS::unbind(); +} + +void KLN89::update(double dt) { + // Run any positional calc's required first + DCLGPS::update(dt); + + _cum_dt += dt; + if(_blink) { + if(_cum_dt > 0.2) { + _cum_dt = 0.0; + _blink = false; + } + } else { + if(_cum_dt > 0.8) { + _cum_dt = 0.0; + _blink = true; + } + } + + _mapHeadingUpdateTimer += dt; + if(_mapHeadingUpdateTimer > 1.0) { + UpdateMapHeading(); + _mapHeadingUpdateTimer = 0.0; + } + + _instrument->Flush(); + _instrument->DrawBackground(); + + if(_dispMsg) { + if(_messageStack.empty()) { + DrawText("No Message", 0, 5, 2); + } else { + // TODO - parse the message string for special strings that indicate degrees signs etc! + DrawText(*_messageStack.begin(), 0, 0, 3); + } + return; + } else { + if(!_messageStack.empty()) { + DrawMessageAlert(); + } + } + + if(_curPage == 6 && _activePage->GetSubPage() == 3) { + // Don't draw the bar on the nav-4 page + } else if(_activePage == _nrst_page) { + // Don't draw the bar on the nearest page + } else { + DrawBar(_curPage); + } + + _activePage->Update(dt); +} + +void KLN89::CreateDefaultFlightPlans() { + // TODO - read these in from preferences.xml or similar instead!!!! + // Create some hardwired default flightplans for testing. + vector ids; + vector wps; + + ids.clear(); + wps.clear(); + ids.push_back("KLSN"); + wps.push_back(GPS_WP_APT); + ids.push_back("VOLTA"); + wps.push_back(GPS_WP_INT); + ids.push_back("C83"); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[5], ids, wps); + + ids.clear(); + wps.clear(); + ids.push_back("KCCR"); + wps.push_back(GPS_WP_APT); + ids.push_back("SUZYE"); + wps.push_back(GPS_WP_INT); + ids.push_back("ALTAM"); + wps.push_back(GPS_WP_INT); + ids.push_back("C83"); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[4], ids, wps); + + ids.clear(); + wps.clear(); + ids.push_back("KLVK"); + wps.push_back(GPS_WP_APT); + ids.push_back("OAK"); + wps.push_back(GPS_WP_VOR); + ids.push_back("PORTE"); + wps.push_back(GPS_WP_INT); + ids.push_back("KHAF"); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[3], ids, wps); + + ids.clear(); + wps.clear(); + ids.push_back("KDPA"); + wps.push_back(GPS_WP_APT); + ids.push_back("OBK"); + wps.push_back(GPS_WP_VOR); + ids.push_back("ENW"); + wps.push_back(GPS_WP_VOR); + ids.push_back("KRAC"); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[2], ids, wps); + //cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n'; + + ids.clear(); + wps.clear(); + ids.push_back("KSFO"); + ids.push_back("KOAK"); + wps.push_back(GPS_WP_APT); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[1], ids, wps); + + ids.clear(); + wps.clear(); + //ids.push_back("KOSH"); + ids.push_back("KSFO"); + ids.push_back("KHAF"); + ids.push_back("OSI"); + ids.push_back("KSQL"); + //ids.push_back("KPAO"); + //ids.push_back("KHWD"); + wps.push_back(GPS_WP_APT); + wps.push_back(GPS_WP_APT); + wps.push_back(GPS_WP_VOR); + wps.push_back(GPS_WP_APT); + //wps.push_back(GPS_WP_APT); + //wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[0], ids, wps); + + /* + ids.clear(); + wps.clear(); + ids.push_back("KLVK"); + ids.push_back("KHWD"); + wps.push_back(GPS_WP_APT); + wps.push_back(GPS_WP_APT); + CreateFlightPlan(_flightPlans[0], ids, wps); + */ +} + +void KLN89::Knob1Right1() { + if(_mode == KLN89_MODE_DISP) { + _activePage->LooseFocus(); + if(_cleanUpPage >= 0) { + _pages[(unsigned int)_cleanUpPage]->CleanUp(); + _cleanUpPage = -1; + } + _curPage++; + if(_curPage >= _pages.size()) _curPage = 0; + _activePage = _pages[_curPage]; + } else { + _activePage->Knob1Right1(); + } + update(0.0); +} + +void KLN89::Knob1Left1() { + if(_mode == KLN89_MODE_DISP) { + _activePage->LooseFocus(); + if(_cleanUpPage >= 0) { + _pages[(unsigned int)_cleanUpPage]->CleanUp(); + _cleanUpPage = -1; + } + if(_curPage == 0) { + _curPage = _pages.size() - 1; + } else { + _curPage--; + } + _activePage = _pages[_curPage]; + } else { + _activePage->Knob1Left1(); + } + update(0.0); +} + +void KLN89::Knob2Left1() { + _activePage->Knob2Left1(); +} + +void KLN89::Knob2Right1() { + _activePage->Knob2Right1(); +} + +void KLN89::CrsrPressed() { + _dispMsg = false; + if(_activePage == _nrst_page) return; // CRSR cannot be switched off on nrst page. + if(_cleanUpPage >= 0) { + _pages[(unsigned int)_cleanUpPage]->CleanUp(); + _cleanUpPage = -1; + } + _entRestoreCrsr = false; + _entJump = -1; + ((KLN89Page*)_activePage)->SetEntInvert(false); + if(_mode == KLN89_MODE_DISP) { + _mode = KLN89_MODE_CRSR; + _activePage->CrsrPressed(); + } else { + _mode = KLN89_MODE_DISP; + _activePage->CrsrPressed(); + } + update(0.0); +} + +void KLN89::EntPressed() { + if(_entJump >= 0) { + if(_curPage < 5) { + // one of the data pages. Signal ent pressed to it here, and ent pressed to the call back page a few lines further down. + // Ie. 2 ent pressed signals in this case is deliberate. + _activePage->EntPressed(); + } + _curPage = _entJump; + _activePage = _pages[(unsigned int)_entJump]; + if(_entRestoreCrsr) _mode = KLN89_MODE_CRSR; + _entJump = -1; + } + if(_activePage == _dir_page) { + _dir_page->EntPressed(); + _mode = KLN89_MODE_DISP; + _activePage = _pages[_curPage]; + } else { + _activePage->EntPressed(); + } +} + +void KLN89::ClrPressed() { + _activePage->ClrPressed(); +} + +void KLN89::DtoPressed() { + if(_activePage != _dir_page) { + // Figure out which waypoint the dir page should display + if(_curPage <= 5) { + // Apt, Vor, Ndb, Int, Usr or Act + if(!_activePage->GetId().empty()) { // Guard against no user waypoints defined + _dir_page->SetId(_activePage->GetId()); + } else { + _dir_page->SetId(_activeWaypoint.id); + } + // } else if(_curPage == 6 && _nav_page->GetSubPage() == 3 && 0) { + // NAV 4 + // TODO + // The && 0 should be && outer knob is out. + } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) { + //cout << "Checking the fpl page!\n"; + // FPL 0 + if(!_activePage->GetId().empty()) { + //cout << "Not empty!!!\n"; + _dir_page->SetId(_activePage->GetId()); + } else { + //cout << "empty :-(\n"; + _dir_page->SetId(_activeWaypoint.id); + } + } else { + _dir_page->SetId(_activeWaypoint.id); + } + // This need to come after the bit before otherwise the FPL page clears it's current ID when it looses focus. + _activePage->LooseFocus(); + _activePage = _dir_page; + _mode = KLN89_MODE_CRSR; + } +} + +void KLN89::NrstPressed() { + if(_activePage != _nrst_page) { + _activePage->LooseFocus(); // TODO - check whether we should call loose focus here + _lastActivePage = _activePage; + _activePage = _nrst_page; + _lastMode = _mode; + _mode = KLN89_MODE_CRSR; + } else { + _activePage = _lastActivePage; + _mode = _lastMode; + } +} + +void KLN89::AltPressed() {} + +void KLN89::OBSPressed() { + DCLGPS::OBSPressed(); + if(_obsMode) { + // if(ORS 02) + _mode = KLN89_MODE_CRSR; + _activePage->OBSPressed(); + } +} + +void KLN89::MsgPressed() { + // TODO - handle persistent messages such as SUA alerting. + // (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available + // until the potential conflict no longer pertains). + if(_dispMsg && _messageStack.size()) { + _messageStack.pop_front(); + } + _dispMsg = !_dispMsg; +} + +void KLN89::DrawBar(int page) { + int px = 1 + (page * 15); + int py = 1; + for(int i=0; i<7; ++i) { + // Ugh - this is crude and inefficient! + _instrument->DrawPixel(px+i, py); + _instrument->DrawPixel(px+i, py+1); + } +} + +// Convert moving map to instrument co-ordinates +void KLN89::MapToInstrument(int &x, int &y) { + x += _xBorder + _xFieldBorder[2] + _xFieldStart[2]; +} + +// Draw a pixel specified in instrument co-ords, but clipped to the map region +//void KLN89::DrawInstrMapPixel(int x, int y) { + +/* +// Clip, translate and draw a map pixel +// If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions. +void KLN89::DrawMapPixel(int x, int y, bool invert) { + if(x < 0 || x > 111 || y < 0 || y > 39) return; + x += _xBorder + _xFieldBorder[2] + _xFieldStart[2]; + _instrument->DrawPixel(x, y, invert); +} +*/ + +// HACK - use something FG provides +static double gps_min(const double &a, const double &b) { + return(a <= b ? a : b); +} + +static double gps_max(const double &a, const double &b) { + return(a >= b ? a : b); +} + +void KLN89::UpdateMapHeading() { + switch(_mapOrientation) { + case 0: // North up + _mapHeading = 0.0; + break; + case 1: // DTK up + _mapHeading = _dtkTrue; + break; + case 2: // Track up + _mapHeading = _track; + break; + } +} + +// The screen area allocated to the moving map is 111 x 40 pixels. +// In North up mode, the user position marker is at 57, 20. (Map co-ords). +void KLN89::DrawMap(bool draw_avs) { + // Set the clipping region to the moving map part of the display + int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2]; + _instrument->SetClipRegion(xstart, 0, xstart + 110, 39); + + _mapScaleUnits = (int)_distUnits; + _mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]); + + //cout << "Map scale = " << _mapScale << '\n'; + + double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000); + + // TODO - use an aligned projection when either DTK or TK up! + FGATCAlignedProjection mapProj(Point3D(_gpsLon * SG_RADIANS_TO_DEGREES, _gpsLat * SG_RADIANS_TO_DEGREES, 0.0), _mapHeading); + + double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f); + + Point3D bottomLeft = mapProj.ConvertFromLocal(Point3D(gps_max(-57.0 * meter_per_pix, -50000), gps_max((_mapOrientation == 0 ? -20.0 * meter_per_pix : -11.0 * meter_per_pix), -25000), 0.0)); + Point3D topRight = mapProj.ConvertFromLocal(Point3D(gps_min(54.0 * meter_per_pix, 50000), gps_min((_mapOrientation == 0 ? 20.0 * meter_per_pix : 29.0 * meter_per_pix), 25000), 0.0)); + + // Draw Airport labels first (but not one's that are waypoints) + // Draw Airports first (but not one's that are waypoints) + // Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not). + // Ditto for SUA + // Then flighttrack + // Then waypoints + // Then waypoint labels (not sure if this should be before or after waypoints) + // Then user pos. + // Annotation then gets drawn by Nav page, NOT this function. + + if(_drawApt && draw_avs) { + airport_list apt; + /* + bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon()); + //cout << "Vors enclosed are: "; + // Draw all the labels first... + for(unsigned int i=0; iid << ' '; + Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0)); + //cout << p << " .... "; + int mx = int(p.x() / meter_per_pix) + 56; + int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + //cout << "mx = " << mx << ", my = " << my << '\n'; + bool right_align = (p.x() < 0.0); + DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align); + // I think that we probably should have -1 in the right_align case above to match the real life instrument. + } + // ...and then all the Apts. + for(unsigned int i=0; iid << ' '; + Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0)); + //cout << p << " .... "; + int mx = int(p.x() / meter_per_pix) + 56; + int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + //cout << "mx = " << mx << ", my = " << my << '\n'; + DrawApt(mx, my); + } + //cout << '\n'; + */ + } + /* + if(_drawVOR && draw_avs) { + Overlays::nav_array_type nav; + bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon()); + //cout << "Vors enclosed are: "; + // Draw all the labels first... + for(unsigned int i=0; iid << ' '; + Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0)); + //cout << p << " .... "; + int mx = int(p.x() / meter_per_pix) + 56; + int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + //cout << "mx = " << mx << ", my = " << my << '\n'; + bool right_align = (p.x() < 0.0); + DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align); + // I think that we probably should have -1 in the right_align case above to match the real life instrument. + } + // ...and then all the VORs. + for(unsigned int i=0; iid << ' '; + Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0)); + //cout << p << " .... "; + int mx = int(p.x() / meter_per_pix) + 56; + int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + //cout << "mx = " << mx << ", my = " << my << '\n'; + DrawVOR(mx, my); + } + //cout << '\n'; + } + */ + + // FlightTrack + if(_activeFP->waypoints.size() > 1) { + vector xvec, yvec, qvec; // qvec stores the quadrant that each waypoint label should + // be drawn in (relative to the waypoint). + // 1 = NE, 2 = SE, 3 = SW, 4 = NW. + double save_h; // Each pass, save a heading from the previous one for label quadrant determination. + bool drawTrack = true; + for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) { + GPSWaypoint* wp0 = _activeFP->waypoints[i-1]; + GPSWaypoint* wp1 = _activeFP->waypoints[i]; + Point3D p0 = mapProj.ConvertToLocal(Point3D(wp0->lon * SG_RADIANS_TO_DEGREES, wp0->lat * SG_RADIANS_TO_DEGREES, 0.0)); + Point3D p1 = mapProj.ConvertToLocal(Point3D(wp1->lon * SG_RADIANS_TO_DEGREES, wp1->lat * SG_RADIANS_TO_DEGREES, 0.0)); + int mx0 = int(p0.x() / meter_per_pix) + 56; + int my0 = int(p0.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + int mx1 = int(p1.x() / meter_per_pix) + 56; + int my1 = int(p1.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10); + if(i == 1) { + xvec.push_back(mx0); + yvec.push_back(my0); + double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES; + // Adjust for map orientation + h -= _mapHeading; + qvec.push_back(GetLabelQuadrant(h)); + //cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n'; + } + xvec.push_back(mx1); + yvec.push_back(my1); + if(drawTrack) { DrawLine(mx0, my0, mx1, my1); } + if(i != 1) { + double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES; + // Adjust for map orientation + h -= _mapHeading; + qvec.push_back(GetLabelQuadrant(save_h, h)); + } + save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES; + // Adjust for map orientation + save_h -= _mapHeading; + if(i == _activeFP->waypoints.size() - 1) { + qvec.push_back(GetLabelQuadrant(save_h)); + } + // Don't draw flight track beyond the missed approach point of an approach + if(_approachLoaded) { + //cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n'; + //cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n'; + if(wp1->appType == GPS_MAP) { + drawTrack = false; + } + } + } + // ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size()); + for(unsigned int i=0; i 2); + bool top = (qvec[i] == 1 || qvec[i] == 4); + // TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths, + // or all before or all afterwards. Doesn't matter a huge deal though. + DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align); + } + } + + // User pos + if(_mapOrientation == 0) { + // North up + DrawUser1(56, 19); + } else if(_mapOrientation == 1) { + // DTK up + DrawUser1(56, 10); + } else if(_mapOrientation == 2) { + // TK up + DrawUser2(56, 10); + } else { + // Heading up + // TODO - don't know what to do here! + } + + // And finally, reset the clip region to stop the rest of the code going pear-shaped! + _instrument->ResetClipRegion(); +} + +// Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it). +// Heading specified FROM the waypoint. +// 4 | 1 +// ----- +// 3 | 2 +int KLN89::GetLabelQuadrant(double h) { + while(h < 0.0) h += 360.0; + while(h > 360.0) h -= 360.0; + if(h < 90.0) return(3); + if(h < 180.0) return(4); + if(h < 270.0) return(1); + return(2); +} + +// Get the quadrant to draw the label of an en-route waypoint, +// with BOTH tracks specified as headings FROM the waypoint. +// 4 | 1 +// ----- +// 3 | 2 +int KLN89::GetLabelQuadrant(double h1, double h2) { + while(h1 < 0.0) h1 += 360.0; + while(h1 > 360.0) h1 -= 360.0; + while(h2 < 0.0) h2 += 360.0; + while(h2 > 360.0) h2 -= 360.0; + double max_min_diff = 0.0; + int quad; + for(int i=0; i<4; ++i) { + double h = 45 + (90 * i); + double diff1 = fabs(h - h1); + if(diff1 > 180) diff1 = 360 - diff1; + double diff2 = fabs(h - h2); + if(diff2 > 180) diff2 = 360 - diff2; + double min_diff = gps_min(diff1, diff2); + if(min_diff > max_min_diff) { + max_min_diff = min_diff; + quad = i + 1; + } + } + //cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n'; + return(quad); +} + +// Draw the diamond style of user pos +// +// o +// oxo +// oxxxo +// oxxxxxo +// oxxxo +// oxo +// o +// +void KLN89::DrawUser1(int x, int y) { + MapToInstrument(x, y); + int min_j = 0, max_j = 0; + for(int i=-3; i<=3; ++i) { + for(int j=min_j; j<=max_j; ++j) { + _instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false)); + } + if(i < 0) { + min_j--; + max_j++; + } else { + min_j++; + max_j--; + } + } +} + +// Draw the airplane style of user pos +// Define the origin to be the midpoint of the *fuselage* +void KLN89::DrawUser2(int x, int y) { + MapToInstrument(x, y); + + // Draw the background as three black quads first + _instrument->DrawQuad(x-2, y-3, x+2, y-1, true); + _instrument->DrawQuad(x-3, y, x+3, y+2, true); + _instrument->DrawQuad(x-1, y+3, x+1, y+3, true); + + if(_pixelated) { + for(int j=y-2; j<=y+2; ++j) { + _instrument->DrawPixel(x, j); + } + for(int i=x-1; i<=x+1; ++i) { + _instrument->DrawPixel(i, y-2); + } + for(int i=x-2; i<=x+2; ++i) { + _instrument->DrawPixel(i, y+1); + } + } else { + _instrument->DrawQuad(x, y-2, x, y+2); + _instrument->DrawQuad(x-1, y-2, x+1, y-2); + _instrument->DrawQuad(x-2, y+1, x+2, y+1); + } +} + +// Draw an airport symbol on the moving map +// +// ooo +// ooxoo +// oxxxo +// ooxoo +// ooo +// +void KLN89::DrawApt(int x, int y) { + MapToInstrument(x, y); + + int j = y-2; + int i; + for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true); + ++j; + for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false)); + ++j; + for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (abs(i - x) > 1 ? true : false)); + ++j; + for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false)); + ++j; + for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true); +} + +// Draw a waypoint on the moving map +// +// ooooo +// oxxxo +// oxxxo +// oxxxo +// ooooo +// +void KLN89::DrawWaypoint(int x, int y) { + MapToInstrument(x, y); + _instrument->SetDebugging(true); + + // Draw black background + _instrument->DrawQuad(x-2, y-2, x+2, y+2, true); + + // Draw the coloured square + if(_pixelated) { + for(int i=x-1; i<=x+1; ++i) { + for(int j=y-1; j<=y+1; ++j) { + _instrument->DrawPixel(i, j); + } + } + } else { + _instrument->DrawQuad(x-1, y-1, x+1, y+1); + } + _instrument->SetDebugging(false); +} + +// Draw a VOR on the moving map +// +// ooooo +// oxxxo +// oxoxo +// oxxxo +// ooooo +// +void KLN89::DrawVOR(int x, int y) { + // Cheat - draw a waypoint and then a black pixel in the middle. + // Need to call Waypoint draw *before* translating co-ords. + DrawWaypoint(x, y); + MapToInstrument(x, y); + _instrument->DrawPixel(x, y, true); +} + +// Draw a line on the moving map +void KLN89::DrawLine(int x1, int y1, int x2, int y2) { + MapToInstrument(x1, y1); + MapToInstrument(x2, y2); + _instrument->DrawLine(x1, y1, x2, y2); +} + +void KLN89::DrawMapUpArrow(int x, int y) { + MapToInstrument(x, y); + if(_pixelated) { + for(int j=0; j<7; ++j) { + _instrument->DrawPixel(x + 2, y + j); + } + } else { + _instrument->DrawQuad(x+2, y, x+2, y+6); + } + _instrument->DrawPixel(x, y+4); + _instrument->DrawPixel(x+1, y+5); + _instrument->DrawPixel(x+3, y+5); + _instrument->DrawPixel(x+4, y+4); +} + +// Draw a quad on the moving map +void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) { + MapToInstrument(x1, y1); + MapToInstrument(x2, y2); + _instrument->DrawQuad(x1, y1, x2, y2, invert); +} + +// Draw an airport or waypoint label on the moving map +// Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label. +// The black background quad will automatically overlap this by 1 pixel. +void KLN89::DrawLabel(string s, int x1, int y1, bool right_align) { + MapToInstrument(x1, y1); + if(!right_align) { + for(unsigned int i=0; i=0; --i) { + char c = s[i]; + x1 -= DrawSmallChar(c, x1, y1, right_align); + x1--; + } + } +} + +void KLN89::DrawCDI() { + // Scale + for(int i=0; i<5; ++i) { + DrawSpecialChar(2, 2, 3+i, 2); + DrawSpecialChar(1, 2, 9+i, 2); + } + + int field = 2; + int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2; + int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + + // Deflection bar + // Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD. + // Maximum deflection is 37 pixels left, or 38 pixels right !?! + double xtd = CalcCrossTrackDeviation(); + int deflect; + if(_cdiScaleTransition) { + double dots = (xtd / _currentCdiScale) * 5.0; + deflect = (int)(dots * 7.0 * -1.0); + // TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1. Possibly! + } else { + if(0 == _currentCdiScaleIndex) { // 5.0nm FSD => 1 nm per dot => 7 pixels per nm. + deflect = (int)(xtd * 7.0 * -1.0); // The -1.0 is because we move the 'needle' indicating the course, not the plane. + } else if(1 == _currentCdiScaleIndex) { + deflect = (int)(xtd * 35.0 * -1.0); + } else { // 0.3 == _cdiScale + deflect = (int)(xtd * 116.6666666667 * -1.0); + } + } + if(deflect > 38) deflect = 38; + if(deflect < -37) deflect = -37; + if(_pixelated) { + for(int j=0; j<9; ++j) { + _instrument->DrawPixel(px + deflect, py+j); + _instrument->DrawPixel(px + deflect + 1, py+j); + } + } else { + _instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8); + } + + // To/From indicator + px-=4; + py+=2; + for(int j=4; j>=0; --j) { + int k = 10 - (2*j); + for(int i=0; iDrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j)); + // At the extremities, draw the outlining dark pixel + if(i == 0 || i == k-1) { + _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true); + } + } + } +} + +void KLN89::DrawLegTail(int py) { + int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2]; + py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2]; + + px++; + py+=3; + py++; // Hack - not sure if this represents a border issue. + + for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i); + for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9); +} + +void KLN89::DrawLongLegTail(int py) { + int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2]; + py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2]; + + px++; + py+=3; + py++; // Hack - not sure if this represents a border issue. + + for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i); + for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18); +} + +void KLN89::DrawHalfLegTail(int py) { +} + +void KLN89::DrawDivider() { + int px = _xFieldStart[2] - 1; + int py = _yBorder; + for(int i=0; i<36; ++i) { + _instrument->DrawPixel(px, py+i); + } +} + +void KLN89::DrawEnt(int field, int px, int py) { + px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1; + + px++; // Not sure why we need px++, but it seems to work! + py++; + + // E + for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i); + _instrument->DrawPixel(px+1, py); + _instrument->DrawPixel(px+2, py); + _instrument->DrawPixel(px+1, py+2); + _instrument->DrawPixel(px+1, py+4); + _instrument->DrawPixel(px+2, py+4); + + px += 4; + // N + for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i); + _instrument->DrawPixel(px+1, py+2); + _instrument->DrawPixel(px+2, py+1); + for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i); + + px += 5; + // T + _instrument->DrawPixel(px, py+3); + for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i); + _instrument->DrawPixel(px+2, py+3); +} + +void KLN89::DrawMessageAlert() { + // TODO - draw the proper message indicator + if(!_blink) { + int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1]; + int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1; + + px++; // Not sure why we need px++, but it seems to work! + py++; + + DrawText(" ", 1, 0, 1, false, 99); + _instrument->DrawQuad(px+1, py-1, px+2, py+5, true); + _instrument->DrawQuad(px+3, py+3, px+3, py+5, true); + _instrument->DrawQuad(px+4, py+2, px+4, py+4, true); + _instrument->DrawQuad(px+5, py+1, px+6, py+3, true); + _instrument->DrawQuad(px+7, py+2, px+7, py+4, true); + _instrument->DrawQuad(px+8, py+3, px+8, py+5, true); + _instrument->DrawQuad(px+9, py-1, px+10, py+5, true); + } +} + +void KLN89::Underline(int field, int px, int py, int len) { + px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + for(int i=0; i<(len*7); ++i) { + _instrument->DrawPixel(px, py); + ++px; + } +} + +void KLN89::DrawKPH(int field, int cx, int cy) { + // Add some border + int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + + px++; + py++; + + for(int j=0; j<=4; ++j) { + _instrument->DrawPixel(px, py + 2 +j); + _instrument->DrawPixel(px + 8, py + j); + if(j <= 1) { + _instrument->DrawPixel(px + 11, py + j); + _instrument->DrawPixel(px + 9 + j, py + 2); + } + } + + for(int i=0; i<=6; ++i) { + if(i <= 2) { + _instrument->DrawPixel(px + 1 + i, py + 4 + i); + _instrument->DrawPixel(px + 1 + i, py + (4 - i)); + } + _instrument->DrawPixel(px + 2 + i, py + i); + } +} + +void KLN89::DrawDTO(int field, int cx, int cy) { + DrawSpecialChar(6, field, cx, cy); + if(!(_waypointAlert && _blink)) { + DrawSpecialChar(3, field, cx+1, cy); + } + + int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + + px++; + py++; + + // Fill in the gap between the 'D' and the arrow. + _instrument->DrawPixel(px+5, py+3); +} + +// Takes character position +void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) { + // Ignore field for now + // Add some border + px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + + // Draw an orange background for inverted characters + if(invert) { + for(int i=0; i<7; ++i) { + for(int j=0; j<9; ++j) { + _instrument->DrawPixel(px + i, py + j); + } + } + } + + if(c < 33) return; // space + + // Render normal decimal points in bold floats + if(c == '.') bold = false; + + ++py; // Shift the char up by one pixel + for(int j=7; j>=0; --j) { + char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]); + // Don't do the last column for now (ie. j = 1, not 0) + for(int i=5; i>=0; --i) { + if(c1 & (01 << i)) { + _instrument->DrawPixel(px, py, invert); + } + ++px; + } + px -= 6; + ++py; + } +} + +// Takes pixel position +void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) { + + if(draw_background) { + _instrument->DrawQuad(x, y, x+6, y+8, true); + } + + if(c < 33) return; // space + + ++y; // Shift the char up by one pixel + for(int j=7; j>=0; --j) { + char c1 = UpperAlpha[c-33][j]; + // Don't do the last column for now (ie. j = 1, not 0) + for(int i=5; i>=0; --i) { + if(c1 & (01 << i)) { + _instrument->DrawPixel(x, y); + } + ++x; + } + x -= 6; + ++y; + } +} + +// Takes instrument pixel co-ordinates. +// Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true. +// The return value is the pixel width of the visible portion +int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) { + // calculate the index into the SmallChar array + int idx; + if(c > 47 && c < 58) { + // number + idx = c - 48; + } else if(c > 64 && c < 91) { + // Uppercase letter + idx = c - 55; + } else { + return(0); + } + + char n = SmallChar[idx][0]; // Width of visible portion + if(align_right) x -= n; + + // background + _instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true); + + for(int j=7; j>=3; --j) { + char c1 = SmallChar[idx][j]; + for(int i=n-1; i>=0; --i) { + if(c1 & (01 << i)) { + _instrument->DrawPixel(x, y); + } + ++x; + } + x -= n; + ++y; + } + + return(n); +} + +// Takes character position +void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) { + if(c > 7) { + cout << "ERROR - requested special char outside array bounds!\n"; + return; // Increment this as SpecialChar grows + } + + // Convert character to pixel position. + // Ignore field for now + // Add some border + int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field]; + int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field]; + ++py; // Total hack - the special chars were coming out 1 pixel too low! + for(int i=7; i>=0; --i) { + char c1 = SpecialChar[(int)c][i]; + // Don't do the last column for now (ie. j = 1, not 0) + for(int j=5; j>=0; --j) { + if(c1 & (01 << j)) { + _instrument->DrawPixel(px, py); + } + ++px; + } + px -= 6; + ++py; + } +} + +void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) { + for(int i = 0; i < (int)s.size(); ++i) { + DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99)); + } +} + +void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) { + MapToInstrument(x, y); + if(draw_background) { + //_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true); + _instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true); + // The minus 1 and minus 2 are an ugly hack to disguise the fact that I've lost track of exactly what's going on! + } + + for(int i = 0; i < (int)s.size(); ++i) { + DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y); + } +} + +void KLN89::DrawLatitude(double d, int field, int px, int py) { + DrawChar((d >= 0 ? 'N' : 'S'), field, px, py); + d = fabs(d); + px += 1; + // TODO - sanity check input to ensure major lat field can only ever by 2 chars wide + char buf[8]; + // Don't know whether to zero pad the below for single digits or not? + //cout << d << ", " << (int)d << '\n'; + // 3 not 2 in size before for trailing \0 + int n = snprintf(buf, 3, "%i", (int)d); + string s = buf; + //cout << s << "... " << n << '\n'; + DrawText(s, field, px+(3-n), py); + n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f); + s = buf; + px += 3; + DrawSpecialChar(0, field, px, py); // Degrees symbol + px++; + DrawText(s, field, px, py); +} + +void KLN89::DrawLongitude(double d, int field, int px, int py) { + DrawChar((d >= 0 ? 'E' : 'W'), field, px, py); + d = fabs(d); + px += 1; + // TODO - sanity check input to ensure major lat field can only ever be 2 chars wide + char buf[8]; + // Don't know whether to zero pad the below for single digits or not? + //cout << d << ", " << (int)d << '\n'; + // 4 not 3 in size before for trailing \0 + int n = snprintf(buf, 4, "%i", (int)d); + string s = buf; + //cout << s << "... " << n << '\n'; + DrawText(s, field, px+(3-n), py); + n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f); + s = buf; + px += 3; + DrawSpecialChar(0, field, px, py); // Degrees symbol + px++; + DrawText(s, field, px, py); +} + +void KLN89::DrawFreq(double d, int field, int px, int py) { + if(d >= 1000) d /= 100.0f; + char buf[8]; + snprintf(buf, 7, "%6.2f", d); + string s = buf; + DrawText(s, field, px, py); +} + +void KLN89::DrawTime(double time, int field, int px, int py) { + int hrs = (int)(time / 3600); + int mins = (int)(ceil((time - (hrs * 3600)) / 60.0)); + char buf[10]; + int n; + if(time >= 60.0) { + // Draw hr:min + n = snprintf(buf, 9, "%i:%02i", hrs, mins); + } else { + // Draw :secs + n = snprintf(buf, 4, ":%02i", (int)time); + } + string s = buf; + DrawText(s, field, px - n + 1, py); +} + +void KLN89::DrawHeading(int h, int field, int px, int py) { + char buf[4]; + snprintf(buf, 4, "%i", h); + string s = buf; + DrawText(s, field, px - s.size(), py); + DrawSpecialChar(0, field, px, py); // Degrees symbol +} + +void KLN89::DrawDist(double d, int field, int px, int py) { + d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001); + char buf[10]; + snprintf(buf, 9, "%i", (int)(d + 0.5)); + string s = buf; + s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"); + DrawText(s, field, px - s.size() + 1, py); +} + +void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) { + // TODO - implement variable decimal places + v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0); + char buf[10]; + snprintf(buf, 9, "%i", (int)(v + 0.5)); + string s = buf; + if(_velUnits == GPS_VEL_UNITS_KT) { + s += "kt"; + DrawText(s, field, px - s.size() + 1, py); + } else { + DrawText(s, field, px - s.size() - 1, py); + DrawKPH(field, px - 1, py); + } +} + +void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) { + DrawChar('>', field, px, py); + char buf[8]; + double h; + if(to_flag) { + h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon); + } else { + h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon); + } + while(h < 0.0) h += 360.0; + while(h > 360.0) h -= 360.0; + snprintf(buf, 4, "%3i", (int)(h + 0.5)); + string s = buf; + if(!(cursel && _blink)) { + DrawText(s, field, px + 4 - s.size(), py); + DrawSpecialChar(0, field, px+4, py); + DrawText((to_flag ? "To" : "Fr"), field, px+5, py); + if(cursel) Underline(field, px + 1, py, 6); + } + //double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon); + //d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001); + double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon); + d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001); + if(d >= 100.0) { + snprintf(buf, 7, "%5i", (int)(d + 0.5)); + } else { + snprintf(buf, 7, "%4.1f", d); + } + s = buf; + DrawText(s, field, px + 12 - s.size(), py); + DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py); +} + +char KLN89::IncChar(char c, bool gap, bool wrap) { + if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9'); + if(c == 'Z') return('0'); + if(c == ' ') return('A'); + return(c + 1); +} + +char KLN89::DecChar(char c, bool gap, bool wrap) { + if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A'); + if(c == '0') return('Z'); + if(c == ' ') return('9'); + return(c - 1); +} diff --git a/src/Instrumentation/KLN89/kln89.hxx b/src/Instrumentation/KLN89/kln89.hxx new file mode 100644 index 000000000..992063e24 --- /dev/null +++ b/src/Instrumentation/KLN89/kln89.hxx @@ -0,0 +1,269 @@ +// kln89_page.hxx - a class to manage the simulation of a KLN89 +// GPS unit. Note that this is primarily the +// simulation of the user interface and display +// - the core GPS calculations such as position +// and waypoint sequencing are done (or should +// be done) by FG code. +// +// Written by David Luff, started 2005. +// +// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + +#ifndef _KLN89_HXX +#define _KLN89_HXX + +#include +#include "kln89_page.hxx" + +const int KLN89MapScales[2][21] = {{1, 2, 3, 5, 7, 10, 12, 15, 17, 20, 25, 30, 40, 60, 80, 100, 120, 160, 240, 320, 500}, + {2, 4, 6, 9, 13, 18, 22, 28, 32, 37, 46, 55, 75, 110, 150, 185, 220, 300, 440, 600, 925}}; + +enum KLN89Mode { + KLN89_MODE_DISP, + KLN89_MODE_CRSR +}; + +/* +const char* KLN89TimeCodes[20] = { "UTC", "GST", "GDT", "ATS", "ATD", "EST", "EDT", "CST", "CDT", "MST", + "MDT", "PST", "PDT", "AKS", "AKD", "HAS", "HAD", "SST", "SDT", "LCL" }; +*/ + +// Used for storing airport town and county mapped by ID, since currently FG does not store this +typedef map airport_id_str_map_type; +typedef airport_id_str_map_type::iterator airport_id_str_map_iterator; + +class KLN89 : public DCLGPS { + + friend class KLN89Page; + friend class KLN89AptPage; + friend class KLN89VorPage; + friend class KLN89NDBPage; + friend class KLN89IntPage; + friend class KLN89UsrPage; + friend class KLN89ActPage; + friend class KLN89NavPage; + friend class KLN89FplPage; + friend class KLN89CalPage; + friend class KLN89SetPage; + friend class KLN89OthPage; + friend class KLN89DirPage; + friend class KLN89NrstPage; + +public: + KLN89(RenderArea2D* instrument); + ~KLN89(); + + void bind(); + void unbind(); + void update(double dt); + + inline void SetTurnAnticipation(bool b) { _turnAnticipationEnabled = b; } + inline bool GetTurnAnticipation() { return(_turnAnticipationEnabled); } + + inline void SetSuaAlertEnabled(bool b) { _suaAlertEnabled = b; } + inline bool GetSuaAlertEnabled() { return(_suaAlertEnabled); } + + inline void SetAltAlertEnabled(bool b) { _altAlertEnabled = b; } + inline bool GetAltAlertEnabled() { return(_altAlertEnabled); } + + inline bool GetMsgAlert() const { return(!_messageStack.empty()); } + + void Knob1Right1(); + void Knob1Left1(); + void Knob2Right1(); + void Knob2Left1(); + void CrsrPressed(); + void EntPressed(); + void ClrPressed(); + void DtoPressed(); + void NrstPressed(); + void AltPressed(); + void OBSPressed(); + void MsgPressed(); + + void CreateDefaultFlightPlans(); + +private: + //----------------------- Drawing functions which take CHARACTER units ------------------------- + // Render string s in display field field at position x, y + // WHERE POSITION IS IN CHARACTER UNITS! + // zero y at bottom? + // invert: -1 => no inversion, 0 -> n => 1 char - s[invert] gets inverted, 99 => entire string gets inverted + void DrawText(const string& s, int field, int px, int py, bool bold = false, int invert = -1); + + void DrawLatitude(double d, int field, int px, int py); + void DrawLongitude(double d, int field, int px, int py); + + // Draw a frequency as xxx.xx + void DrawFreq(double d, int field, int px, int py); + + // Draw a time in seconds as hh:mm + // NOTE: px is RIGHT JUSTIFIED! + void DrawTime(double time, int field, int px, int py); + + // Draw an integer heading, where px specifies the position of the degrees sign at the RIGHT of the value. + void DrawHeading(int h, int field, int px, int py); + + // Draw a distance spec'd as nm as an integer (TODO - may need 1 decimal place if < 100) where px specifies RHS of units. + // Some uses definately don't want decimal place though (as at present), so would have to be arg. + void DrawDist(double d, int field, int px, int py); + + // Draw a speed specifed in knots. px is RHS of the units. Can draw up to 2 decimal places. + void DrawSpeed(double v, int field, int px, int py, int decimals = 0); + + void Underline(int field, int px, int py, int len); + + // Render a char at a given position as above (position in CHARACTER units) + void DrawChar(char c, int field, int px, int py, bool bold = false, bool invert = false); + void DrawSpecialChar(char c, int field, int cx, int cy, bool bold = false); + + // Draws the dir/dist field at the bottom of the main field + void DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag = true, bool cursel = false); + // + //--------------------------------- end char units ----------------------------------------------- + + //----------------------- Drawing functions which take PIXEL units ------------------------------ + // + // Takes instrument *pixel* co-ordinates NOT character units + // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true. + // The return value is the pixel width of the visible portion + int DrawSmallChar(char c, int x, int y, bool align_right = false); + + void DrawFreeChar(char c, int x, int y, bool draw_background = false); + // + //----------------------------------- end pixel unit functions ----------------------------------- + + void DrawDivider(); + + void DrawEnt(int field = 1, int px = 0, int py = 1); + + void DrawMessageAlert(); + + void DrawKPH(int field, int cx, int cy); + + void DrawDTO(int field, int cx, int cy); + + // Draw the bar that indicates which page we're on (zero-based) + void DrawBar(int page); + + void DrawCDI(); + + void DrawLegTail(int py); + void DrawLongLegTail(int py); + void DrawHalfLegTail(int py); + + void UpdateMapHeading(); + + // Draw the moving map + // Apt, VOR and SUA drawing can be suspended by setting draw_avs to false, without affecting the stored drawing preference state. + void DrawMap(bool draw_avs = true); + + // Set whether the display should be draw pixelated (more primatives, but might be closer to real-life) + // or not (in which case it is assumed that pixels are square and can be merged into quads). + bool _pixelated; + + // Flashing output should be hidden when blink is true + bool _blink; + + double _cum_dt; + + // In Crsr mode, CRSR pressed events are passed to the active page, in disp mode they change which page is active + KLN89Mode _mode; + // And the facility to save a mode + KLN89Mode _lastMode; + + // Increment/Decrement a character in the KLN89 A-Z,0-9 scheme. + // Set gap to true to get a space between A and 9 when wrapping, set wrap to false to disable wrap. + char IncChar(char c, bool gap = false, bool wrap = true); + char DecChar(char c, bool gap = false, bool wrap = true); + + // Hackish + int _entJump; // The page to jump back to if ent is pressed. -1 indicates no jump + bool _entRestoreCrsr; // Indicates that pressing ENT at this point should restore cursor mode + + // Misc pages + // Direct To + GPSPage* _dir_page; + // Nearest + GPSPage* _nrst_page; + + // Moving-map display stuff + int _mapOrientation; // 0 => North (true) up, 1 => DTK up, 2 => TK up, 3 => heading up (only when connected to external heading source). + double _mapHeading; // Degrees. The actual map heading gets updated at a lower frequency than DrawMap() is called at, hence we need to store it. + double _mapHeadingUpdateTimer; // Timer to determine when to update the above. + bool _mapScaleAuto; // Indicates that map should autoscale when true. + int _mapScaleIndex; // Index into array of available map scales. + int _mapScaleUnits; // 0 => nm, 1 => km. + double _mapScale; // nm or km from aircraft position to top of map. + // Note that aircraft position differs depending on orientation, but 'scale' retains the same meaning, + // so the scale per pixel alters to suit the defined scale when the rendered aircraft position changes. + bool _drawSUA; // special user airspace + bool _drawVOR; + bool _drawApt; + + // Convert map to instrument coordinates + void MapToInstrument(int &x, int &y); + + // The following map drawing functions all take MAP co-ordinates, NOT instrument co-ordinates! + + // Draw the diamond style of user pos + void DrawUser1(int x, int y); + + // Draw the airplane style of user pos + void DrawUser2(int x, int y); + + // Draw an airport symbol on the moving map + void DrawApt(int x, int y); + + // Draw a waypoint on the moving map + void DrawWaypoint(int x, int y); + + // Draw a VOR on the moving map + void DrawVOR(int x, int y); + + // Draw an airport or waypoint label on the moving map + // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label. + // The black background quad will automatically overlap this by 1 pixel. + void DrawLabel(string s, int x1, int y1, bool right_align = false); + + int GetLabelQuadrant(double h); + int GetLabelQuadrant(double h1, double h2); + + // Draw a line on the moving map + void DrawLine(int x1, int y1, int x2, int y2); + + // Draw normal sized text on the moving map + void DrawMapText(const string& s, int x, int y, bool draw_background = false); + + void DrawMapUpArrow(int x, int y); + + // Draw a Quad on the moving map + void DrawMapQuad(int x1, int y1, int x2, int y2, bool invert = false); + + // Airport town and state mapped by ID, since currently FG does not store this + airport_id_str_map_type _airportTowns; + airport_id_str_map_type _airportStates; + + // NOTE - It is a deliberate decision not to have a proper message page class, + // since button events get directed to the page that was active before the + // message was displayed, not the message page itself. + bool _dispMsg; // Set true while the message page is being displayed +}; + +#endif // _KLN89_HXX diff --git a/src/Instrumentation/KLN89/kln89_page.cxx b/src/Instrumentation/KLN89/kln89_page.cxx new file mode 100644 index 000000000..153164839 --- /dev/null +++ b/src/Instrumentation/KLN89/kln89_page.cxx @@ -0,0 +1,204 @@ +// kln89_page.cxx - base class for the "pages" that +// are used in the KLN89 GPS unit simulation. +// +// Written by David Luff, started 2005. +// +// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + +#include "kln89_page.hxx" + +KLN89Page::KLN89Page(KLN89* parent) +: GPSPage(parent) { + _kln89 = (KLN89*)parent; + _entInvert = false; + _to_flag = true; +} + +KLN89Page::~KLN89Page() { +} + +void KLN89Page::Update(double dt) { + bool crsr = (_kln89->_mode == KLN89_MODE_CRSR ? true : false); + bool nav1 = (_name == "NAV" && _subPage == 0); + bool nav4 = (_name == "NAV" && _subPage == 3); + // The extra level of check for the ACT page is necessary since + // ACT is implemented by using the other waypoint pages as + // appropriate. + bool act = (_kln89->_activePage->GetName() == "ACT"); + _kln89->DrawDivider(); + if(crsr) { + if(!nav4) _kln89->DrawText("*CRSR*", 1, 0, 0); + if(_uLinePos == 0) _kln89->Underline(1, 3, 1, 3); + } else { + if(!nav4) { + if(act) { + _kln89->DrawText("ACT", 1, 0, 0); + } else { + _kln89->DrawText(_name, 1, 0, 0); + } + if(_name == "DIR") { + // Don't draw a subpage number + } else if(_name == "USR" || _name == "FPL") { + // Zero-based + _kln89->DrawText(GPSitoa(_subPage), 1, 4, 0); + } else { + // One-based + _kln89->DrawText(GPSitoa(_subPage+1), 1, 4, 0); + } + } + } + if(crsr && _uLinePos == 0 && _kln89->_blink) { + // Don't draw + } else { + if(_kln89->_obsMode) { + _kln89->DrawText(GPSitoa(_kln89->_obsHeading), 1, 3, 1); + } else { + _kln89->DrawText("Leg", 1, 3, 1); + } + } + _kln89->DrawText((_kln89->GetDistVelUnitsSI() ? "km" : "nm"), 1, 4, 3); + GPSWaypoint* awp = _parent->GetActiveWaypoint(); + if(_kln89->_navFlagged) { + _kln89->DrawText("--.-", 1, 0 ,3); + // Only nav1 still gets speed drawn if nav is flagged - not ACT + if(!nav1) _kln89->DrawText("------", 1, 0, 2); + } else { + char buf[8]; + float f = _parent->GetDistToActiveWaypoint() * (_kln89->GetDistVelUnitsSI() ? 0.001 : SG_METER_TO_NM); + snprintf(buf, 5, (f >= 100.0 ? "%4.0f" : "%4.1f"), f); + string s = buf; + _kln89->DrawText(s, 1, 4 - s.size(), 3, true); + // Draw active waypoint ID, except for + // nav1, act, and any waypoint pages matching + // active waypoint that need speed drawn instead. + if(act || nav1 || (awp && awp->id == _id)) { + _kln89->DrawSpeed(_kln89->_groundSpeed_kts, 1, 5, 2); + } else { + if(!(_kln89->_waypointAlert && _kln89->_blink)) _kln89->DrawText(awp->id, 1, 0, 2); + } + } + /* + if(_noNrst) { + _kln89->DrawText(" No ", 1, 0, 1, false, 99); + _kln89->DrawText(" Nrst ", 1, 0, 0, false, 99); + } + */ + if(_scratchpadMsg) { + _kln89->DrawText(_scratchpadLine1, 1, 0, 1, false, 99); + _kln89->DrawText(_scratchpadLine2, 1, 0, 0, false, 99); + _scratchpadTimer += dt; + if(_scratchpadTimer > 4.0) { + _scratchpadMsg = false; + _scratchpadTimer = 0.0; + } + } +} + +void KLN89Page::ShowScratchpadMessage(string line1, string line2) { + _scratchpadLine1 = line1; + _scratchpadLine2 = line2; + _scratchpadTimer = 0.0; + _scratchpadMsg = true; +} + +void KLN89Page::Knob1Left1() { + if(_kln89->_mode == KLN89_MODE_CRSR) { + if(_uLinePos > 0) _uLinePos--; + } +} + +void KLN89Page::Knob1Right1() { + if(_kln89->_mode == KLN89_MODE_CRSR) { + if(_uLinePos < _maxULinePos) _uLinePos++; + } +} + +void KLN89Page::Knob2Left1() { + if(_kln89->_mode != KLN89_MODE_CRSR) { + GPSPage::Knob2Left1(); + } else { + if(_uLinePos == 0 && _kln89->_obsMode) { + _kln89->_obsHeading--; + if(_kln89->_obsHeading < 0) { + _kln89->_obsHeading += 360; + } + _kln89->SetOBSFromWaypoint(); + } + } +} + +void KLN89Page::Knob2Right1() { + if(_kln89->_mode != KLN89_MODE_CRSR) { + GPSPage::Knob2Right1(); + } else { + if(_uLinePos == 0 && _kln89->_obsMode) { + _kln89->_obsHeading++; + if(_kln89->_obsHeading > 359) { + _kln89->_obsHeading -= 360; + } + _kln89->SetOBSFromWaypoint(); + } + } +} + +void KLN89Page::CrsrPressed() { + // Stick some sensible defaults in + if(_kln89->_obsMode) { + _uLinePos = 0; + } else { + _uLinePos = 1; + } + _maxULinePos = 1; +} + +void KLN89Page::EntPressed() {} +void KLN89Page::ClrPressed() {} +void KLN89Page::DtoPressed() {} +void KLN89Page::NrstPressed() {} +void KLN89Page::AltPressed() {} + +void KLN89Page::OBSPressed() { + if(_kln89->_obsMode) { + // If ORS2 and not slaved to gps + _uLinePos = 0; + } else { + // Don't leave the cursor on in the leg position. + if(_uLinePos == 0) { + _kln89->_mode = KLN89_MODE_DISP; + } + } +} + +void KLN89Page::MsgPressed() {} + +void KLN89Page::CleanUp() { + _kln89->_cleanUpPage = -1; +} + +void KLN89Page::LooseFocus() { + _entInvert = false; +} + +void KLN89Page::SetId(string s) { + _id = s; +} + +string KLN89Page::GetId() { + return(_id); +} diff --git a/src/Instrumentation/KLN89/kln89_page.hxx b/src/Instrumentation/KLN89/kln89_page.hxx new file mode 100644 index 000000000..251436b5d --- /dev/null +++ b/src/Instrumentation/KLN89/kln89_page.hxx @@ -0,0 +1,93 @@ +// kln89_page.hxx - base class for the "pages" that +// are used in the KLN89 GPS unit simulation. +// +// Written by David Luff, started 2005. +// +// Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ + +#ifndef _KLN89_PAGE_HXX +#define _KLN89_PAGE_HXX + +#include +#include "kln89.hxx" + +class KLN89; + +class KLN89Page : public GPSPage { + +public: + KLN89Page(KLN89* parent); + virtual ~KLN89Page(); + virtual void Update(double dt); + virtual void Knob1Left1(); + virtual void Knob1Right1(); + virtual void Knob2Left1(); + virtual void Knob2Right1(); + virtual void CrsrPressed(); + virtual void EntPressed(); + virtual void ClrPressed(); + // Even though some/all of the buttons below aren't processed directly by the current page, + // the current page often needs to save or change some state when they are pressed, and + // hence should provide a function to handle them. + virtual void DtoPressed(); + virtual void NrstPressed(); + virtual void AltPressed(); + virtual void OBSPressed(); + virtual void MsgPressed(); + + // See base class comments for this. + virtual void CleanUp(); + + // ditto + virtual void LooseFocus(); + + inline void SetEntInvert(bool b) { _entInvert = b; } + + // Get / Set a waypoint id, NOT the page name! + virtual void SetId(string s); + virtual string GetId(); + +protected: + KLN89* _kln89; + + // Underline position in cursor mode is not persistant when subpage is changed - hence we only need one variable per page for it. + // Note that pos 0 is special - this is the leg pos in field 1, so pos will normally be set to 1 when crsr is pressed. + // Also note that in general it doesn't seem to wrap. + unsigned int _uLinePos; + unsigned int _maxULinePos; + + // This is NOT the main gps to/from flag - derived page classes can use this flag + // for any purpose, typically whether a radial bearing should be displayed to or from. + bool _to_flag; // true for TO, false for FROM + + // Invert ID and display ENT in field 1 + bool _entInvert; + + string _id; // The ID of the waypoint that the page is displaying. + // Doesn't make sense for all pages, but does for all the data pages. + + void ShowScratchpadMessage(string line1, string line2); + + bool _scratchpadMsg; // Set true when there is a scratchpad message to display + double _scratchpadTimer; // Used for displaying the scratchpad messages for the right amount of time. + string _scratchpadLine1; + string _scratchpadLine2; +}; + +#endif // _KLN89_PAGE_HXX