1 // kln89_page.cxx - a class to manage the simulation of a KLN89
2 // GPS unit. Note that this is primarily the
3 // simulation of the user interface and display
4 // - the core GPS calculations such as position
5 // and waypoint sequencing are done (or should
6 // be done) by FG code.
8 // Written by David Luff, started 2005.
10 // Copyright (C) 2005 - David C Luff - david.luff@nottingham.ac.uk
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 // General Public License for more details.
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #include "kln89_page.hxx"
30 #include "kln89_page_apt.hxx"
31 #include "kln89_page_vor.hxx"
32 #include "kln89_page_ndb.hxx"
33 #include "kln89_page_int.hxx"
34 #include "kln89_page_usr.hxx"
35 #include "kln89_page_act.hxx"
36 #include "kln89_page_nav.hxx"
37 #include "kln89_page_fpl.hxx"
38 #include "kln89_page_cal.hxx"
39 #include "kln89_page_set.hxx"
40 #include "kln89_page_oth.hxx"
41 #include "kln89_page_dir.hxx"
42 #include "kln89_page_nrst.hxx"
43 #include "kln89_symbols.hxx"
46 #include <ATC/ATCProjection.hxx>
47 #include <Main/fg_props.hxx>
48 #include <simgear/math/point3d.hxx>
52 KLN89::KLN89(RenderArea2D* instrument)
53 : DCLGPS(instrument) {
54 _mode = KLN89_MODE_DISP;
61 // ..Field..[0] => no fields in action
79 GPSPage* apt_page = new KLN89AptPage(this);
80 _pages.push_back(apt_page);
81 GPSPage* vor_page = new KLN89VorPage(this);
82 _pages.push_back(vor_page);
83 GPSPage* ndb_page = new KLN89NDBPage(this);
84 _pages.push_back(ndb_page);
85 GPSPage* int_page = new KLN89IntPage(this);
86 _pages.push_back(int_page);
87 GPSPage* usr_page = new KLN89UsrPage(this);
88 _pages.push_back(usr_page);
89 GPSPage* act_page = new KLN89ActPage(this);
90 _pages.push_back(act_page);
91 GPSPage* nav_page = new KLN89NavPage(this);
92 _pages.push_back(nav_page);
93 GPSPage* fpl_page = new KLN89FplPage(this);
94 _pages.push_back(fpl_page);
95 GPSPage* cal_page = new KLN89CalPage(this);
96 _pages.push_back(cal_page);
97 GPSPage* set_page = new KLN89SetPage(this);
98 _pages.push_back(set_page);
99 GPSPage* oth_page = new KLN89OthPage(this);
100 _pages.push_back(oth_page);
101 _nPages = _pages.size();
105 _dir_page = new KLN89DirPage(this);
106 _nrst_page = new KLN89NrstPage(this);
108 _activePage = apt_page;
114 _maxFlightPlans = 26;
115 for(unsigned int i=0; i<_maxFlightPlans; ++i) {
116 GPSFlightPlan* fp = new GPSFlightPlan;
117 fp->waypoints.clear();
118 _flightPlans.push_back(fp);
120 _activeFP = _flightPlans[0];
124 _entRestoreCrsr = false;
131 _mapHeadingUpdateTimer = 0.0;
135 //_mapScaleIndex = 20;
136 _mapScaleIndex = 7; // I think that the above is more accurate for no-flightplan default, but this is more sane for initial testing!
137 _mapScaleAuto = true;
139 // 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
140 // TODO - do this better one day!
141 _airportTowns["KSFO"] = "San Francisco";
142 _airportTowns["KSQL"] = "San Carlos";
143 _airportTowns["KPAO"] = "Palo Alto";
144 _airportTowns["KNUQ"] = "Mountain View";
145 _airportTowns["KSJC"] = "San Jose";
146 _airportTowns["KRHV"] = "San Jose";
147 _airportTowns["E16"] = "San Martin";
148 _airportTowns["KWVI"] = "Watsonville";
149 _airportTowns["KOAK"] = "Oakland";
150 _airportTowns["KHWD"] = "Hayward";
151 _airportTowns["KLVK"] = "Livermore";
152 _airportTowns["KCCR"] = "Concord";
153 _airportTowns["KTCY"] = "Tracy";
154 _airportTowns["KSCK"] = "Stockton";
155 _airportTowns["KHAF"] = "Half Moon Bay";
157 _airportStates["KSFO"] = "CA";
158 _airportStates["KSQL"] = "CA";
159 _airportStates["KPAO"] = "CA";
160 _airportStates["KNUQ"] = "CA";
161 _airportStates["KSJC"] = "CA";
162 _airportStates["KRHV"] = "CA";
163 _airportStates["E16"] = "CA";
164 _airportStates["KWVI"] = "CA";
165 _airportStates["KOAK"] = "CA";
166 _airportStates["KHWD"] = "CA";
167 _airportStates["KLVK"] = "CA";
168 _airportStates["KCCR"] = "CA";
169 _airportStates["KTCY"] = "CA";
170 _airportStates["KSCK"] = "CA";
171 _airportStates["KHAF"] = "CA";
175 for(unsigned int i=0; i<_pages.size(); ++i) {
181 for(unsigned int i=0; i<_maxFlightPlans; ++i) {
183 delete _flightPlans[i];
188 fgTie("/instrumentation/gps/message-alert", this, &KLN89::GetMsgAlert);
192 void KLN89::unbind() {
193 fgUntie("/instrumentation/gps/message-alert");
197 void KLN89::update(double dt) {
198 // Run any positional calc's required first
214 _mapHeadingUpdateTimer += dt;
215 if(_mapHeadingUpdateTimer > 1.0) {
217 _mapHeadingUpdateTimer = 0.0;
220 _instrument->Flush();
221 _instrument->DrawBackground();
224 if(_messageStack.empty()) {
225 DrawText("No Message", 0, 5, 2);
227 // TODO - parse the message string for special strings that indicate degrees signs etc!
228 DrawText(*_messageStack.begin(), 0, 0, 3);
232 if(!_messageStack.empty()) {
237 if(_curPage == 6 && _activePage->GetSubPage() == 3) {
238 // Don't draw the bar on the nav-4 page
239 } else if(_activePage == _nrst_page) {
240 // Don't draw the bar on the nearest page
245 _activePage->Update(dt);
248 void KLN89::CreateDefaultFlightPlans() {
249 // TODO - read these in from preferences.xml or similar instead!!!!
250 // Create some hardwired default flightplans for testing.
252 vector<GPSWpType> wps;
256 ids.push_back("KLSN");
257 wps.push_back(GPS_WP_APT);
258 ids.push_back("VOLTA");
259 wps.push_back(GPS_WP_INT);
260 ids.push_back("C83");
261 wps.push_back(GPS_WP_APT);
262 CreateFlightPlan(_flightPlans[5], ids, wps);
266 ids.push_back("KCCR");
267 wps.push_back(GPS_WP_APT);
268 ids.push_back("SUZYE");
269 wps.push_back(GPS_WP_INT);
270 ids.push_back("ALTAM");
271 wps.push_back(GPS_WP_INT);
272 ids.push_back("C83");
273 wps.push_back(GPS_WP_APT);
274 CreateFlightPlan(_flightPlans[4], ids, wps);
278 ids.push_back("KLVK");
279 wps.push_back(GPS_WP_APT);
280 ids.push_back("OAK");
281 wps.push_back(GPS_WP_VOR);
282 ids.push_back("PORTE");
283 wps.push_back(GPS_WP_INT);
284 ids.push_back("KHAF");
285 wps.push_back(GPS_WP_APT);
286 CreateFlightPlan(_flightPlans[3], ids, wps);
290 ids.push_back("KDPA");
291 wps.push_back(GPS_WP_APT);
292 ids.push_back("OBK");
293 wps.push_back(GPS_WP_VOR);
294 ids.push_back("ENW");
295 wps.push_back(GPS_WP_VOR);
296 ids.push_back("KRAC");
297 wps.push_back(GPS_WP_APT);
298 CreateFlightPlan(_flightPlans[2], ids, wps);
299 //cout << "Size of FP2 WP list is " << _flightPlans[2]->waypoints.size() << '\n';
303 ids.push_back("KSFO");
304 ids.push_back("KOAK");
305 wps.push_back(GPS_WP_APT);
306 wps.push_back(GPS_WP_APT);
307 CreateFlightPlan(_flightPlans[1], ids, wps);
311 //ids.push_back("KOSH");
312 ids.push_back("KSFO");
313 ids.push_back("KHAF");
314 ids.push_back("OSI");
315 ids.push_back("KSQL");
316 //ids.push_back("KPAO");
317 //ids.push_back("KHWD");
318 wps.push_back(GPS_WP_APT);
319 wps.push_back(GPS_WP_APT);
320 wps.push_back(GPS_WP_VOR);
321 wps.push_back(GPS_WP_APT);
322 //wps.push_back(GPS_WP_APT);
323 //wps.push_back(GPS_WP_APT);
324 CreateFlightPlan(_flightPlans[0], ids, wps);
329 ids.push_back("KLVK");
330 ids.push_back("KHWD");
331 wps.push_back(GPS_WP_APT);
332 wps.push_back(GPS_WP_APT);
333 CreateFlightPlan(_flightPlans[0], ids, wps);
337 void KLN89::Knob1Right1() {
338 if(_mode == KLN89_MODE_DISP) {
339 _activePage->LooseFocus();
340 if(_cleanUpPage >= 0) {
341 _pages[(unsigned int)_cleanUpPage]->CleanUp();
345 if(_curPage >= _pages.size()) _curPage = 0;
346 _activePage = _pages[_curPage];
348 _activePage->Knob1Right1();
353 void KLN89::Knob1Left1() {
354 if(_mode == KLN89_MODE_DISP) {
355 _activePage->LooseFocus();
356 if(_cleanUpPage >= 0) {
357 _pages[(unsigned int)_cleanUpPage]->CleanUp();
361 _curPage = _pages.size() - 1;
365 _activePage = _pages[_curPage];
367 _activePage->Knob1Left1();
372 void KLN89::Knob2Left1() {
373 _activePage->Knob2Left1();
376 void KLN89::Knob2Right1() {
377 _activePage->Knob2Right1();
380 void KLN89::CrsrPressed() {
382 if(_activePage == _nrst_page) return; // CRSR cannot be switched off on nrst page.
383 if(_cleanUpPage >= 0) {
384 _pages[(unsigned int)_cleanUpPage]->CleanUp();
387 _entRestoreCrsr = false;
389 ((KLN89Page*)_activePage)->SetEntInvert(false);
390 if(_mode == KLN89_MODE_DISP) {
391 _mode = KLN89_MODE_CRSR;
392 _activePage->CrsrPressed();
394 _mode = KLN89_MODE_DISP;
395 _activePage->CrsrPressed();
400 void KLN89::EntPressed() {
403 // one of the data pages. Signal ent pressed to it here, and ent pressed to the call back page a few lines further down.
404 // Ie. 2 ent pressed signals in this case is deliberate.
405 _activePage->EntPressed();
408 _activePage = _pages[(unsigned int)_entJump];
409 if(_entRestoreCrsr) _mode = KLN89_MODE_CRSR;
412 if(_activePage == _dir_page) {
413 _dir_page->EntPressed();
414 _mode = KLN89_MODE_DISP;
415 _activePage = _pages[_curPage];
417 _activePage->EntPressed();
421 void KLN89::ClrPressed() {
422 _activePage->ClrPressed();
425 void KLN89::DtoPressed() {
426 if(_activePage != _dir_page) {
427 // Figure out which waypoint the dir page should display
429 // Apt, Vor, Ndb, Int, Usr or Act
430 if(!_activePage->GetId().empty()) { // Guard against no user waypoints defined
431 _dir_page->SetId(_activePage->GetId());
433 _dir_page->SetId(_activeWaypoint.id);
435 // } else if(_curPage == 6 && _nav_page->GetSubPage() == 3 && 0) {
438 // The && 0 should be && outer knob is out.
439 } else if(_curPage == 7 && _activePage->GetSubPage() == 0 && _mode == KLN89_MODE_CRSR) {
440 //cout << "Checking the fpl page!\n";
442 if(!_activePage->GetId().empty()) {
443 //cout << "Not empty!!!\n";
444 _dir_page->SetId(_activePage->GetId());
446 //cout << "empty :-(\n";
447 _dir_page->SetId(_activeWaypoint.id);
450 _dir_page->SetId(_activeWaypoint.id);
452 // This need to come after the bit before otherwise the FPL page clears it's current ID when it looses focus.
453 _activePage->LooseFocus();
454 _activePage = _dir_page;
455 _mode = KLN89_MODE_CRSR;
459 void KLN89::NrstPressed() {
460 if(_activePage != _nrst_page) {
461 _activePage->LooseFocus(); // TODO - check whether we should call loose focus here
462 _lastActivePage = _activePage;
463 _activePage = _nrst_page;
465 _mode = KLN89_MODE_CRSR;
467 _activePage = _lastActivePage;
472 void KLN89::AltPressed() {}
474 void KLN89::OBSPressed() {
475 DCLGPS::OBSPressed();
478 _mode = KLN89_MODE_CRSR;
479 _activePage->OBSPressed();
483 void KLN89::MsgPressed() {
484 // TODO - handle persistent messages such as SUA alerting.
485 // (The message annunciation flashes before first view, but afterwards remains continuously lit with the message available
486 // until the potential conflict no longer pertains).
487 if(_dispMsg && _messageStack.size()) {
488 _messageStack.pop_front();
490 _dispMsg = !_dispMsg;
493 void KLN89::DrawBar(int page) {
494 int px = 1 + (page * 15);
496 for(int i=0; i<7; ++i) {
497 // Ugh - this is crude and inefficient!
498 _instrument->DrawPixel(px+i, py);
499 _instrument->DrawPixel(px+i, py+1);
503 // Convert moving map to instrument co-ordinates
504 void KLN89::MapToInstrument(int &x, int &y) {
505 x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
508 // Draw a pixel specified in instrument co-ords, but clipped to the map region
509 //void KLN89::DrawInstrMapPixel(int x, int y) {
512 // Clip, translate and draw a map pixel
513 // If we didn't need per-pixel clipping, it would be cheaper to translate object rather than pixel positions.
514 void KLN89::DrawMapPixel(int x, int y, bool invert) {
515 if(x < 0 || x > 111 || y < 0 || y > 39) return;
516 x += _xBorder + _xFieldBorder[2] + _xFieldStart[2];
517 _instrument->DrawPixel(x, y, invert);
521 // HACK - use something FG provides
522 static double gps_min(const double &a, const double &b) {
523 return(a <= b ? a : b);
526 static double gps_max(const double &a, const double &b) {
527 return(a >= b ? a : b);
530 void KLN89::UpdateMapHeading() {
531 switch(_mapOrientation) {
536 _mapHeading = _dtkTrue;
539 _mapHeading = _track;
544 // The screen area allocated to the moving map is 111 x 40 pixels.
545 // In North up mode, the user position marker is at 57, 20. (Map co-ords).
546 void KLN89::DrawMap(bool draw_avs) {
547 // Set the clipping region to the moving map part of the display
548 int xstart = _xBorder + _xFieldBorder[2] + _xFieldStart[2];
549 _instrument->SetClipRegion(xstart, 0, xstart + 110, 39);
551 _mapScaleUnits = (int)_distUnits;
552 _mapScale = (double)(KLN89MapScales[_mapScaleUnits][_mapScaleIndex]);
554 //cout << "Map scale = " << _mapScale << '\n';
556 double mapScaleMeters = _mapScale * (_mapScaleUnits == 0 ? SG_NM_TO_METER : 1000);
558 // TODO - use an aligned projection when either DTK or TK up!
559 FGATCAlignedProjection mapProj(Point3D(_gpsLon * SG_RADIANS_TO_DEGREES, _gpsLat * SG_RADIANS_TO_DEGREES, 0.0), _mapHeading);
561 double meter_per_pix = (_mapOrientation == 0 ? mapScaleMeters / 20.0f : mapScaleMeters / 29.0f);
563 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));
564 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));
566 // Draw Airport labels first (but not one's that are waypoints)
567 // Draw Airports first (but not one's that are waypoints)
568 // Ditto for VORs (not sure if SUA/VOR/Airport ordering is important or not).
572 // Then waypoint labels (not sure if this should be before or after waypoints)
574 // Annotation then gets drawn by Nav page, NOT this function.
576 if(_drawApt && draw_avs) {
579 bool have_apt = _overlays->FindArpByRegion(&apt, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
580 //cout << "Vors enclosed are: ";
581 // Draw all the labels first...
582 for(unsigned int i=0; i<apt.size(); ++i) {
583 //cout << nav[i]->id << ' ';
584 Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
585 //cout << p << " .... ";
586 int mx = int(p.x() / meter_per_pix) + 56;
587 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
588 //cout << "mx = " << mx << ", my = " << my << '\n';
589 bool right_align = (p.x() < 0.0);
590 DrawLabel(apt[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
591 // I think that we probably should have -1 in the right_align case above to match the real life instrument.
593 // ...and then all the Apts.
594 for(unsigned int i=0; i<apt.size(); ++i) {
595 //cout << nav[i]->id << ' ';
596 Point3D p = mapProj.ConvertToLocal(Point3D(apt[i]->lon * SG_RADIANS_TO_DEGREES, apt[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
597 //cout << p << " .... ";
598 int mx = int(p.x() / meter_per_pix) + 56;
599 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
600 //cout << "mx = " << mx << ", my = " << my << '\n';
607 if(_drawVOR && draw_avs) {
608 Overlays::nav_array_type nav;
609 bool have_vor = _overlays->FindVorByRegion(&nav, bottomLeft.lat(), bottomLeft.lon(), topRight.lat(), topRight.lon());
610 //cout << "Vors enclosed are: ";
611 // Draw all the labels first...
612 for(unsigned int i=0; i<nav.size(); ++i) {
613 //cout << nav[i]->id << ' ';
614 Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
615 //cout << p << " .... ";
616 int mx = int(p.x() / meter_per_pix) + 56;
617 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
618 //cout << "mx = " << mx << ", my = " << my << '\n';
619 bool right_align = (p.x() < 0.0);
620 DrawLabel(nav[i]->id, mx + (right_align ? -2 : 3), my + (p.y() < 0.0 ? -7 : 3), right_align);
621 // I think that we probably should have -1 in the right_align case above to match the real life instrument.
623 // ...and then all the VORs.
624 for(unsigned int i=0; i<nav.size(); ++i) {
625 //cout << nav[i]->id << ' ';
626 Point3D p = mapProj.ConvertToLocal(Point3D(nav[i]->lon * SG_RADIANS_TO_DEGREES, nav[i]->lat * SG_RADIANS_TO_DEGREES, 0.0));
627 //cout << p << " .... ";
628 int mx = int(p.x() / meter_per_pix) + 56;
629 int my = int(p.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
630 //cout << "mx = " << mx << ", my = " << my << '\n';
638 if(_activeFP->waypoints.size() > 1) {
639 vector<int> xvec, yvec, qvec; // qvec stores the quadrant that each waypoint label should
640 // be drawn in (relative to the waypoint).
641 // 1 = NE, 2 = SE, 3 = SW, 4 = NW.
642 double save_h; // Each pass, save a heading from the previous one for label quadrant determination.
643 bool drawTrack = true;
644 for(unsigned int i=1; i<_activeFP->waypoints.size(); ++i) {
645 GPSWaypoint* wp0 = _activeFP->waypoints[i-1];
646 GPSWaypoint* wp1 = _activeFP->waypoints[i];
647 Point3D p0 = mapProj.ConvertToLocal(Point3D(wp0->lon * SG_RADIANS_TO_DEGREES, wp0->lat * SG_RADIANS_TO_DEGREES, 0.0));
648 Point3D p1 = mapProj.ConvertToLocal(Point3D(wp1->lon * SG_RADIANS_TO_DEGREES, wp1->lat * SG_RADIANS_TO_DEGREES, 0.0));
649 int mx0 = int(p0.x() / meter_per_pix) + 56;
650 int my0 = int(p0.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
651 int mx1 = int(p1.x() / meter_per_pix) + 56;
652 int my1 = int(p1.y() / meter_per_pix) + (_mapOrientation == 0 ? 19 : 10);
656 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
657 // Adjust for map orientation
659 qvec.push_back(GetLabelQuadrant(h));
660 //cout << "i = " << i << ", h = " << h << ", qvec[0] = " << qvec[0] << '\n';
664 if(drawTrack) { DrawLine(mx0, my0, mx1, my1); }
666 double h = GetGreatCircleCourse(wp0->lat, wp0->lon, wp1->lat, wp1->lon) * SG_RADIANS_TO_DEGREES;
667 // Adjust for map orientation
669 qvec.push_back(GetLabelQuadrant(save_h, h));
671 save_h = GetGreatCircleCourse(wp1->lat, wp1->lon, wp0->lat, wp0->lon) * SG_RADIANS_TO_DEGREES;
672 // Adjust for map orientation
673 save_h -= _mapHeading;
674 if(i == _activeFP->waypoints.size() - 1) {
675 qvec.push_back(GetLabelQuadrant(save_h));
677 // Don't draw flight track beyond the missed approach point of an approach
678 if(_approachLoaded) {
679 //cout << "Waypoints are " << wp0->id << " and " << wp1->id << '\n';
680 //cout << "Types are " << wp0->appType << " and " << wp1->appType << '\n';
681 if(wp1->appType == GPS_MAP) {
686 // ASSERT(xvec.size() == yvec.size() == qvec.size() == _activeFP->waypoints.size());
687 for(unsigned int i=0; i<xvec.size(); ++i) {
688 DrawWaypoint(xvec[i], yvec[i]);
689 bool right_align = (qvec[i] > 2);
690 bool top = (qvec[i] == 1 || qvec[i] == 4);
691 // TODO - not sure if labels should be drawn in sequence with waypoints and flightpaths,
692 // or all before or all afterwards. Doesn't matter a huge deal though.
693 DrawLabel(_activeFP->waypoints[i]->id, xvec[i] + (right_align ? -2 : 3), yvec[i] + (top ? 3 : -7), right_align);
698 if(_mapOrientation == 0) {
701 } else if(_mapOrientation == 1) {
704 } else if(_mapOrientation == 2) {
709 // TODO - don't know what to do here!
712 // And finally, reset the clip region to stop the rest of the code going pear-shaped!
713 _instrument->ResetClipRegion();
716 // Get the quadrant to draw the label of the start or end waypoint (i.e. one with only one track from it).
717 // Heading specified FROM the waypoint.
721 int KLN89::GetLabelQuadrant(double h) {
722 while(h < 0.0) h += 360.0;
723 while(h > 360.0) h -= 360.0;
724 if(h < 90.0) return(3);
725 if(h < 180.0) return(4);
726 if(h < 270.0) return(1);
730 // Get the quadrant to draw the label of an en-route waypoint,
731 // with BOTH tracks specified as headings FROM the waypoint.
735 int KLN89::GetLabelQuadrant(double h1, double h2) {
736 while(h1 < 0.0) h1 += 360.0;
737 while(h1 > 360.0) h1 -= 360.0;
738 while(h2 < 0.0) h2 += 360.0;
739 while(h2 > 360.0) h2 -= 360.0;
740 double max_min_diff = 0.0;
742 for(int i=0; i<4; ++i) {
743 double h = 45 + (90 * i);
744 double diff1 = fabs(h - h1);
745 if(diff1 > 180) diff1 = 360 - diff1;
746 double diff2 = fabs(h - h2);
747 if(diff2 > 180) diff2 = 360 - diff2;
748 double min_diff = gps_min(diff1, diff2);
749 if(min_diff > max_min_diff) {
750 max_min_diff = min_diff;
754 //cout << "GetLabelQuadrant, h1 = " << h1 << ", h2 = " << h2 << ", quad = " << quad << '\n';
758 // Draw the diamond style of user pos
768 void KLN89::DrawUser1(int x, int y) {
769 MapToInstrument(x, y);
770 int min_j = 0, max_j = 0;
771 for(int i=-3; i<=3; ++i) {
772 for(int j=min_j; j<=max_j; ++j) {
773 _instrument->DrawPixel(x+j, y+i, (j == min_j || j == max_j ? true : false));
785 // Draw the airplane style of user pos
786 // Define the origin to be the midpoint of the *fuselage*
787 void KLN89::DrawUser2(int x, int y) {
788 MapToInstrument(x, y);
790 // Draw the background as three black quads first
791 _instrument->DrawQuad(x-2, y-3, x+2, y-1, true);
792 _instrument->DrawQuad(x-3, y, x+3, y+2, true);
793 _instrument->DrawQuad(x-1, y+3, x+1, y+3, true);
796 for(int j=y-2; j<=y+2; ++j) {
797 _instrument->DrawPixel(x, j);
799 for(int i=x-1; i<=x+1; ++i) {
800 _instrument->DrawPixel(i, y-2);
802 for(int i=x-2; i<=x+2; ++i) {
803 _instrument->DrawPixel(i, y+1);
806 _instrument->DrawQuad(x, y-2, x, y+2);
807 _instrument->DrawQuad(x-1, y-2, x+1, y-2);
808 _instrument->DrawQuad(x-2, y+1, x+2, y+1);
812 // Draw an airport symbol on the moving map
820 void KLN89::DrawApt(int x, int y) {
821 MapToInstrument(x, y);
825 for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
827 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
829 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (abs(i - x) > 1 ? true : false));
831 for(i=x-2; i<=x+2; ++i) _instrument->DrawPixel(i, j, (i != x ? true : false));
833 for(i=x-1; i<=x+1; ++i) _instrument->DrawPixel(i, j, true);
836 // Draw a waypoint on the moving map
844 void KLN89::DrawWaypoint(int x, int y) {
845 MapToInstrument(x, y);
846 _instrument->SetDebugging(true);
848 // Draw black background
849 _instrument->DrawQuad(x-2, y-2, x+2, y+2, true);
851 // Draw the coloured square
853 for(int i=x-1; i<=x+1; ++i) {
854 for(int j=y-1; j<=y+1; ++j) {
855 _instrument->DrawPixel(i, j);
859 _instrument->DrawQuad(x-1, y-1, x+1, y+1);
861 _instrument->SetDebugging(false);
864 // Draw a VOR on the moving map
872 void KLN89::DrawVOR(int x, int y) {
873 // Cheat - draw a waypoint and then a black pixel in the middle.
874 // Need to call Waypoint draw *before* translating co-ords.
876 MapToInstrument(x, y);
877 _instrument->DrawPixel(x, y, true);
880 // Draw a line on the moving map
881 void KLN89::DrawLine(int x1, int y1, int x2, int y2) {
882 MapToInstrument(x1, y1);
883 MapToInstrument(x2, y2);
884 _instrument->DrawLine(x1, y1, x2, y2);
887 void KLN89::DrawMapUpArrow(int x, int y) {
888 MapToInstrument(x, y);
890 for(int j=0; j<7; ++j) {
891 _instrument->DrawPixel(x + 2, y + j);
894 _instrument->DrawQuad(x+2, y, x+2, y+6);
896 _instrument->DrawPixel(x, y+4);
897 _instrument->DrawPixel(x+1, y+5);
898 _instrument->DrawPixel(x+3, y+5);
899 _instrument->DrawPixel(x+4, y+4);
902 // Draw a quad on the moving map
903 void KLN89::DrawMapQuad(int x1, int y1, int x2, int y2, bool invert) {
904 MapToInstrument(x1, y1);
905 MapToInstrument(x2, y2);
906 _instrument->DrawQuad(x1, y1, x2, y2, invert);
909 // Draw an airport or waypoint label on the moving map
910 // Specify position by the map pixel co-ordinate of the left or right, bottom, of the *visible* portion of the label.
911 // The black background quad will automatically overlap this by 1 pixel.
912 void KLN89::DrawLabel(const string& s, int x1, int y1, bool right_align) {
913 MapToInstrument(x1, y1);
915 for(unsigned int i=0; i<s.size(); ++i) {
917 x1 += DrawSmallChar(c, x1, y1);
921 for(int i=(int)(s.size()-1); i>=0; --i) {
923 x1 -= DrawSmallChar(c, x1, y1, right_align);
929 void KLN89::DrawCDI() {
931 for(int i=0; i<5; ++i) {
932 DrawSpecialChar(2, 2, 3+i, 2);
933 DrawSpecialChar(1, 2, 9+i, 2);
937 int px = 8 * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field] + 2;
938 int py = 2 * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
941 // Every 7 pixels deflection left or right is one dot on the scale, and hence 1/5 FSD.
942 // Maximum deflection is 37 pixels left, or 38 pixels right !?!
943 double xtd = CalcCrossTrackDeviation();
945 if(_cdiScaleTransition) {
946 double dots = (xtd / _currentCdiScale) * 5.0;
947 deflect = (int)(dots * 7.0 * -1.0);
948 // TODO - for all these I think I should add 0.5 before casting to int, and *then* multiply by -1. Possibly!
950 if(0 == _currentCdiScaleIndex) { // 5.0nm FSD => 1 nm per dot => 7 pixels per nm.
951 deflect = (int)(xtd * 7.0 * -1.0); // The -1.0 is because we move the 'needle' indicating the course, not the plane.
952 } else if(1 == _currentCdiScaleIndex) {
953 deflect = (int)(xtd * 35.0 * -1.0);
954 } else { // 0.3 == _cdiScale
955 deflect = (int)(xtd * 116.6666666667 * -1.0);
958 if(deflect > 38) deflect = 38;
959 if(deflect < -37) deflect = -37;
961 for(int j=0; j<9; ++j) {
962 _instrument->DrawPixel(px + deflect, py+j);
963 _instrument->DrawPixel(px + deflect + 1, py+j);
966 _instrument->DrawQuad(px + deflect, py, px + deflect + 1, py + 8);
972 for(int j=4; j>=0; --j) {
974 for(int i=0; i<k; ++i) {
975 _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j : py+4-j));
976 // At the extremities, draw the outlining dark pixel
977 if(i == 0 || i == k-1) {
978 _instrument->DrawPixel(px+j+i, (_headingBugTo ? py+j+1 : py+3-j), true);
984 void KLN89::DrawLegTail(int py) {
985 int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
986 py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
990 py++; // Hack - not sure if this represents a border issue.
992 for(int i=0; i<9; ++i) _instrument->DrawPixel(px, py+i);
993 for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+9);
996 void KLN89::DrawLongLegTail(int py) {
997 int px = 0 * 7 + _xBorder + _xFieldBorder[2] + _xFieldStart[2];
998 py = py * 9 + _yBorder + _yFieldBorder[2] + _yFieldStart[2];
1002 py++; // Hack - not sure if this represents a border issue.
1004 for(int i=0; i<18; ++i) _instrument->DrawPixel(px, py+i);
1005 for(int i2=0; i2<5; ++i2) _instrument->DrawPixel(px+i2, py+18);
1008 void KLN89::DrawHalfLegTail(int py) {
1011 void KLN89::DrawDivider() {
1012 int px = _xFieldStart[2] - 1;
1014 for(int i=0; i<36; ++i) {
1015 _instrument->DrawPixel(px, py+i);
1019 void KLN89::DrawEnt(int field, int px, int py) {
1020 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1021 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field] + 1;
1023 px++; // Not sure why we need px++, but it seems to work!
1027 for(int i=0; i<5; ++i) _instrument->DrawPixel(px, py+i);
1028 _instrument->DrawPixel(px+1, py);
1029 _instrument->DrawPixel(px+2, py);
1030 _instrument->DrawPixel(px+1, py+2);
1031 _instrument->DrawPixel(px+1, py+4);
1032 _instrument->DrawPixel(px+2, py+4);
1036 for(int i=0; i<4; ++i) _instrument->DrawPixel(px, py+i);
1037 _instrument->DrawPixel(px+1, py+2);
1038 _instrument->DrawPixel(px+2, py+1);
1039 for(int i=0; i<4; ++i) _instrument->DrawPixel(px+3, py+i);
1043 _instrument->DrawPixel(px, py+3);
1044 for(int i=0; i<4; ++i) _instrument->DrawPixel(px+1, py+i);
1045 _instrument->DrawPixel(px+2, py+3);
1048 void KLN89::DrawMessageAlert() {
1049 // TODO - draw the proper message indicator
1051 int px = _xBorder + _xFieldBorder[1] + _xFieldStart[1];
1052 int py = 1 * 9 + _yBorder + _yFieldBorder[1] + _yFieldStart[1] + 1;
1054 px++; // Not sure why we need px++, but it seems to work!
1057 DrawText(" ", 1, 0, 1, false, 99);
1058 _instrument->DrawQuad(px+1, py-1, px+2, py+5, true);
1059 _instrument->DrawQuad(px+3, py+3, px+3, py+5, true);
1060 _instrument->DrawQuad(px+4, py+2, px+4, py+4, true);
1061 _instrument->DrawQuad(px+5, py+1, px+6, py+3, true);
1062 _instrument->DrawQuad(px+7, py+2, px+7, py+4, true);
1063 _instrument->DrawQuad(px+8, py+3, px+8, py+5, true);
1064 _instrument->DrawQuad(px+9, py-1, px+10, py+5, true);
1068 void KLN89::Underline(int field, int px, int py, int len) {
1069 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1070 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1071 for(int i=0; i<(len*7); ++i) {
1072 _instrument->DrawPixel(px, py);
1077 void KLN89::DrawKPH(int field, int cx, int cy) {
1079 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1080 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1085 for(int j=0; j<=4; ++j) {
1086 _instrument->DrawPixel(px, py + 2 +j);
1087 _instrument->DrawPixel(px + 8, py + j);
1089 _instrument->DrawPixel(px + 11, py + j);
1090 _instrument->DrawPixel(px + 9 + j, py + 2);
1094 for(int i=0; i<=6; ++i) {
1096 _instrument->DrawPixel(px + 1 + i, py + 4 + i);
1097 _instrument->DrawPixel(px + 1 + i, py + (4 - i));
1099 _instrument->DrawPixel(px + 2 + i, py + i);
1103 void KLN89::DrawDTO(int field, int cx, int cy) {
1104 DrawSpecialChar(6, field, cx, cy);
1105 if(!(_waypointAlert && _blink)) {
1106 DrawSpecialChar(3, field, cx+1, cy);
1109 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1110 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1115 // Fill in the gap between the 'D' and the arrow.
1116 _instrument->DrawPixel(px+5, py+3);
1119 // Takes character position
1120 void KLN89::DrawChar(char c, int field, int px, int py, bool bold, bool invert) {
1121 // Ignore field for now
1123 px = px * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1124 py = py * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1126 // Draw an orange background for inverted characters
1128 for(int i=0; i<7; ++i) {
1129 for(int j=0; j<9; ++j) {
1130 _instrument->DrawPixel(px + i, py + j);
1135 if(c < 33) return; // space
1137 // Render normal decimal points in bold floats
1138 if(c == '.') bold = false;
1140 ++py; // Shift the char up by one pixel
1141 for(int j=7; j>=0; --j) {
1142 char c1 = (bold ? NumbersBold[c-48][j] : UpperAlpha[c-33][j]);
1143 // Don't do the last column for now (ie. j = 1, not 0)
1144 for(int i=5; i>=0; --i) {
1145 if(c1 & (01 << i)) {
1146 _instrument->DrawPixel(px, py, invert);
1155 // Takes pixel position
1156 void KLN89::DrawFreeChar(char c, int x, int y, bool draw_background) {
1158 if(draw_background) {
1159 _instrument->DrawQuad(x, y, x+6, y+8, true);
1162 if(c < 33) return; // space
1164 ++y; // Shift the char up by one pixel
1165 for(int j=7; j>=0; --j) {
1166 char c1 = UpperAlpha[c-33][j];
1167 // Don't do the last column for now (ie. j = 1, not 0)
1168 for(int i=5; i>=0; --i) {
1169 if(c1 & (01 << i)) {
1170 _instrument->DrawPixel(x, y);
1179 // Takes instrument pixel co-ordinates.
1180 // Position is specified by the bottom of the *visible* portion, by default the left position unless align_right is true.
1181 // The return value is the pixel width of the visible portion
1182 int KLN89::DrawSmallChar(char c, int x, int y, bool align_right) {
1183 // calculate the index into the SmallChar array
1185 if(c > 47 && c < 58) {
1188 } else if(c > 64 && c < 91) {
1195 char n = SmallChar[idx][0]; // Width of visible portion
1196 if(align_right) x -= n;
1199 _instrument->DrawQuad(x - 1, y - 1, x + n, y + 5, true);
1201 for(int j=7; j>=3; --j) {
1202 char c1 = SmallChar[idx][j];
1203 for(int i=n-1; i>=0; --i) {
1204 if(c1 & (01 << i)) {
1205 _instrument->DrawPixel(x, y);
1216 // Takes character position
1217 void KLN89::DrawSpecialChar(char c, int field, int cx, int cy, bool bold) {
1219 cout << "ERROR - requested special char outside array bounds!\n";
1220 return; // Increment this as SpecialChar grows
1223 // Convert character to pixel position.
1224 // Ignore field for now
1226 int px = cx * 7 + _xBorder + _xFieldBorder[field] + _xFieldStart[field];
1227 int py = cy * 9 + _yBorder + _yFieldBorder[field] + _yFieldStart[field];
1228 ++py; // Total hack - the special chars were coming out 1 pixel too low!
1229 for(int i=7; i>=0; --i) {
1230 char c1 = SpecialChar[(int)c][i];
1231 // Don't do the last column for now (ie. j = 1, not 0)
1232 for(int j=5; j>=0; --j) {
1233 if(c1 & (01 << j)) {
1234 _instrument->DrawPixel(px, py);
1243 void KLN89::DrawText(const string& s, int field, int px, int py, bool bold, int invert) {
1244 for(int i = 0; i < (int)s.size(); ++i) {
1245 DrawChar(s[(unsigned int)i], field, px+i, py, bold, (invert == i || invert == 99));
1249 void KLN89::DrawMapText(const string& s, int x, int y, bool draw_background) {
1250 MapToInstrument(x, y);
1251 if(draw_background) {
1252 //_instrument->DrawQuad(x, y, x + (7 * s.size()) - 1, y + 8, true);
1253 _instrument->DrawQuad(x - 1, y, x + (7 * s.size()) - 2, y + 8, true);
1254 // 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!
1257 for(int i = 0; i < (int)s.size(); ++i) {
1258 DrawFreeChar(s[(unsigned int)i], x+(i * 7)-1, y);
1262 void KLN89::DrawLatitude(double d, int field, int px, int py) {
1263 DrawChar((d >= 0 ? 'N' : 'S'), field, px, py);
1266 // TODO - sanity check input to ensure major lat field can only ever by 2 chars wide
1268 // Don't know whether to zero pad the below for single digits or not?
1269 //cout << d << ", " << (int)d << '\n';
1270 // 3 not 2 in size before for trailing \0
1271 int n = snprintf(buf, 3, "%i", (int)d);
1273 //cout << s << "... " << n << '\n';
1274 DrawText(s, field, px+(3-n), py);
1275 n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1278 DrawSpecialChar(0, field, px, py); // Degrees symbol
1280 DrawText(s, field, px, py);
1283 void KLN89::DrawLongitude(double d, int field, int px, int py) {
1284 DrawChar((d >= 0 ? 'E' : 'W'), field, px, py);
1287 // TODO - sanity check input to ensure major lat field can only ever be 2 chars wide
1289 // Don't know whether to zero pad the below for single digits or not?
1290 //cout << d << ", " << (int)d << '\n';
1291 // 4 not 3 in size before for trailing \0
1292 int n = snprintf(buf, 4, "%i", (int)d);
1294 //cout << s << "... " << n << '\n';
1295 DrawText(s, field, px+(3-n), py);
1296 n = snprintf(buf, 7, "%05.2f'", ((double)(d - (int)d)) * 60.0f);
1299 DrawSpecialChar(0, field, px, py); // Degrees symbol
1301 DrawText(s, field, px, py);
1304 void KLN89::DrawFreq(double d, int field, int px, int py) {
1305 if(d >= 1000) d /= 100.0f;
1307 snprintf(buf, 7, "%6.2f", d);
1309 DrawText(s, field, px, py);
1312 void KLN89::DrawTime(double time, int field, int px, int py) {
1313 int hrs = (int)(time / 3600);
1314 int mins = (int)(ceil((time - (hrs * 3600)) / 60.0));
1319 n = snprintf(buf, 9, "%i:%02i", hrs, mins);
1322 n = snprintf(buf, 4, ":%02i", (int)time);
1325 DrawText(s, field, px - n + 1, py);
1328 void KLN89::DrawHeading(int h, int field, int px, int py) {
1330 snprintf(buf, 4, "%i", h);
1332 DrawText(s, field, px - s.size(), py);
1333 DrawSpecialChar(0, field, px, py); // Degrees symbol
1336 void KLN89::DrawDist(double d, int field, int px, int py) {
1337 d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1339 snprintf(buf, 9, "%i", (int)(d + 0.5));
1341 s += (_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km");
1342 DrawText(s, field, px - s.size() + 1, py);
1345 void KLN89::DrawSpeed(double v, int field, int px, int py, int decimal) {
1346 // TODO - implement variable decimal places
1347 v *= (_velUnits == GPS_VEL_UNITS_KT ? 1.0 : 0.51444444444 * 0.001 * 3600.0);
1349 snprintf(buf, 9, "%i", (int)(v + 0.5));
1351 if(_velUnits == GPS_VEL_UNITS_KT) {
1353 DrawText(s, field, px - s.size() + 1, py);
1355 DrawText(s, field, px - s.size() - 1, py);
1356 DrawKPH(field, px - 1, py);
1360 void KLN89::DrawDirDistField(double lat, double lon, int field, int px, int py, bool to_flag, bool cursel) {
1361 DrawChar('>', field, px, py);
1365 h = GetMagHeadingFromTo(_gpsLat, _gpsLon, lat, lon);
1367 h = GetMagHeadingFromTo(lat, lon, _gpsLat, _gpsLon);
1369 while(h < 0.0) h += 360.0;
1370 while(h > 360.0) h -= 360.0;
1371 snprintf(buf, 4, "%3i", (int)(h + 0.5));
1373 if(!(cursel && _blink)) {
1374 DrawText(s, field, px + 4 - s.size(), py);
1375 DrawSpecialChar(0, field, px+4, py);
1376 DrawText((to_flag ? "To" : "Fr"), field, px+5, py);
1377 if(cursel) Underline(field, px + 1, py, 6);
1379 //double d = GetHorizontalSeparation(_gpsLat, _gpsLon, lat, lon);
1380 //d *= (_distUnits == GPS_DIST_UNITS_NM ? SG_METER_TO_NM : 0.001);
1381 double d = GetGreatCircleDistance(_gpsLat, _gpsLon, lat, lon);
1382 d *= (_distUnits == GPS_DIST_UNITS_NM ? 1.0 : SG_NM_TO_METER * 0.001);
1384 snprintf(buf, 7, "%5i", (int)(d + 0.5));
1386 snprintf(buf, 7, "%4.1f", d);
1389 DrawText(s, field, px + 12 - s.size(), py);
1390 DrawText((_distUnits == GPS_DIST_UNITS_NM ? "nm" : "Km"), field, px + 12, py);
1393 char KLN89::IncChar(char c, bool gap, bool wrap) {
1394 if(c == '9') return(wrap ? (gap ? ' ' : 'A') : '9');
1395 if(c == 'Z') return('0');
1396 if(c == ' ') return('A');
1400 char KLN89::DecChar(char c, bool gap, bool wrap) {
1401 if(c == 'A') return(wrap ? (gap ? ' ' : '9') : 'A');
1402 if(c == '0') return('Z');
1403 if(c == ' ') return('9');