diff --git a/src/GUI/FGPUIDialog.cxx b/src/GUI/FGPUIDialog.cxx index a57aeba5e..a7e2dddbf 100644 --- a/src/GUI/FGPUIDialog.cxx +++ b/src/GUI/FGPUIDialog.cxx @@ -977,6 +977,7 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh } else if (type == "map") { MapWidget* mapWidget = new MapWidget(x, y, x + width, y + height); setupObject(mapWidget, props); + _activeWidgets.push_back(mapWidget); return mapWidget; } else if (type == "canvas") { CanvasWidget* canvasWidget = new CanvasWidget( x, y, diff --git a/src/GUI/MapWidget.cxx b/src/GUI/MapWidget.cxx index 3c2cc8ba9..8a5a0b715 100644 --- a/src/GUI/MapWidget.cxx +++ b/src/GUI/MapWidget.cxx @@ -69,7 +69,7 @@ static bool puBoxIntersect(const puBox& a, const puBox& b) return (x0 <= x1) && (y0 <= y1); } - + class MapData; typedef std::vector<MapData*> MapDataVec; @@ -380,6 +380,76 @@ int MapData::_fontDescender = 0; /////////////////////////////////////////////////////////////////////////// +// anonymous namespace +namespace +{ + +class MapAirportFilter : public FGAirport::AirportFilter +{ +public: + MapAirportFilter(SGPropertyNode_ptr nd) + { + _heliports = nd->getBoolValue("show-heliports", false); + _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true); + _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000); + } + + virtual FGPositioned::Type maxType() const { + return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT; + } + + virtual bool passAirport(FGAirport* aApt) const { + if (_hardRunwaysOnly) { + return aApt->hasHardRunwayOfLengthFt(_minLengthFt); + } + + return true; + } + + void showAll() + { + _hardRunwaysOnly = false; + } + +private: + bool _heliports; + bool _hardRunwaysOnly; + double _minLengthFt; +}; + +class NavaidFilter : public FGPositioned::Filter +{ +public: + NavaidFilter(bool fixesEnabled, bool navaidsEnabled) : + _fixes(fixesEnabled), + _navaids(navaidsEnabled) + {} + + virtual bool pass(FGPositioned* aPos) const { + if (_fixes && (aPos->type() == FGPositioned::FIX)) { + // ignore fixes which end in digits - expirmental + if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) { + return false; + } + } + + return true; + } + + virtual FGPositioned::Type minType() const { + return _fixes ? FGPositioned::FIX : FGPositioned::NDB; + } + + virtual FGPositioned::Type maxType() const { + return _navaids ? FGPositioned::VOR : FGPositioned::FIX; + } + +private: + bool _fixes, _navaids; +}; + +} // of anonymous namespace + const int MAX_ZOOM = 12; const int SHOW_DETAIL_ZOOM = 8; const int SHOW_DETAIL2_ZOOM = 5; @@ -544,41 +614,127 @@ void MapWidget::zoomOut() _root->setIntValue("zoom", zoom() - 1); } +void MapWidget::update() +{ + _aircraft = globals->get_aircraft_position(); + + bool mag = _root->getBoolValue("magnetic-headings"); + if (mag != _magneticHeadings) { + clearData(); // flush cached data text, since it often includes heading + _magneticHeadings = mag; + } + + if (_hasPanned) { + _root->setBoolValue("centre-on-aircraft", false); + _hasPanned = false; + } + else if (_root->getBoolValue("centre-on-aircraft")) { + _projectionCenter = _aircraft; + } + + double julianDate = globals->get_time_params()->getJD(); + _magVar->update(_projectionCenter, julianDate); + + _aircraftUp = _root->getBoolValue("aircraft-heading-up"); + if (_aircraftUp) { + _upHeading = fgGetDouble("/orientation/heading-deg"); + } else { + _upHeading = 0.0; + } + + if (_magneticHeadings) { + _displayHeading = (int) fgGetDouble("/orientation/heading-magnetic-deg"); + } else { + _displayHeading = (int) _upHeading; + } + + _cachedZoom = MAX_ZOOM - zoom(); + SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2)); + // compute draw range, including a fudge factor for ILSs and other 'long' + // symbols + _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0; + + + FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history"); + if (history && _root->getBoolValue("draw-flight-history")) { + _flightHistoryPath = history->pathForHistory(); + } else { + _flightHistoryPath.clear(); + } + +// make spatial queries. This can trigger loading of XML files, etc, so we do +// not want to do it in draw(), which can be called from an arbitrary OSG +// rendering thread. + + MapAirportFilter af(_root); + if (_cachedZoom <= SHOW_DETAIL2_ZOOM) { + // show all airports when zoomed in sufficently + af.showAll(); + } + + bool partial = false; + FGPositionedList newItemsToDraw = + FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial); + + bool fixes = _root->getBoolValue("draw-fixes"); + NavaidFilter f(fixes, _root->getBoolValue("draw-navaids")); + if (f.minType() <= f.maxType()) { + FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f); + newItemsToDraw.insert(newItemsToDraw.end(), navs.begin(), navs.end()); + } + + FGPositioned::TypeFilter tf(FGPositioned::COUNTRY); + if (_cachedZoom <= SHOW_DETAIL_ZOOM) { + tf.addType(FGPositioned::CITY); + } + + if (_cachedZoom <= SHOW_DETAIL2_ZOOM) { + tf.addType(FGPositioned::TOWN); + } + + FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &tf); + newItemsToDraw.insert(newItemsToDraw.end(), poi.begin(), poi.end()); + + _itemsToDraw.swap(newItemsToDraw); + + updateAIObjects(); +} + +void MapWidget::updateAIObjects() +{ + if (!_root->getBoolValue("draw-traffic") || (_cachedZoom > SHOW_DETAIL_ZOOM)) { + _aiDrawVec.clear(); + return; + } + + AIDrawVec newDrawVec; + + const SGPropertyNode* ai = fgGetNode("/ai/models", true); + for (int i = 0; i < ai->nChildren(); ++i) { + const SGPropertyNode *model = ai->getChild(i); + // skip bad or dead entries + if (!model || model->getIntValue("id", -1) == -1) { + continue; + } + + SGGeod pos = SGGeod::fromDegFt( + model->getDoubleValue("position/longitude-deg"), + model->getDoubleValue("position/latitude-deg"), + model->getDoubleValue("position/altitude-ft")); + + double dist = SGGeodesy::distanceNm(_projectionCenter, pos); + if (dist > _drawRangeNm) { + continue; + } + + newDrawVec.push_back(DrawAIObject((SGPropertyNode*) model, pos)); + } // of ai/models iteration + + _aiDrawVec.swap(newDrawVec); +} + void MapWidget::draw(int dx, int dy) { - _aircraft = globals->get_aircraft_position(); - - bool mag = _root->getBoolValue("magnetic-headings"); - if (mag != _magneticHeadings) { - clearData(); // flush cached data text, since it often includes heading - _magneticHeadings = mag; - } - - if (_hasPanned) { - _root->setBoolValue("centre-on-aircraft", false); - _hasPanned = false; - } - else if (_root->getBoolValue("centre-on-aircraft")) { - _projectionCenter = _aircraft; - } - - double julianDate = globals->get_time_params()->getJD(); - _magVar->update(_projectionCenter, julianDate); - - bool aircraftUp = _root->getBoolValue("aircraft-heading-up"); - if (aircraftUp) { - _upHeading = fgGetDouble("/orientation/heading-deg"); - } else { - _upHeading = 0.0; - } - - _cachedZoom = MAX_ZOOM - zoom(); - SGGeod topLeft = unproject(SGVec2d(_width/2, _height/2)); - // compute draw range, including a fudge factor for ILSs and other 'long' - // symbols - _drawRangeNm = SGGeodesy::distanceNm(_projectionCenter, topLeft) + 10.0; - -// drawing operations GLint sx = (int) abox.min[0], sy = (int) abox.min[1]; glScissor(dx + sx, dy + sy, _width, _height); @@ -592,7 +748,7 @@ void MapWidget::draw(int dx, int dy) drawLatLonGrid(); - if (aircraftUp) { + if (_aircraftUp) { int textHeight = legendFont.getStringHeight() + 5; // draw heading line @@ -600,23 +756,14 @@ void MapWidget::draw(int dx, int dy) glColor3f(1.0, 1.0, 1.0); drawLine(loc, SGVec2d(loc.x(), (_height / 2) - textHeight)); - int displayHdg; - if (_magneticHeadings) { - displayHdg = (int) fgGetDouble("/orientation/heading-magnetic-deg"); - } else { - displayHdg = (int) _upHeading; - } - double y = (_height / 2) - textHeight; char buf[16]; - ::snprintf(buf, 16, "%d", displayHdg); + ::snprintf(buf, 16, "%d", _displayHeading); int sw = legendFont.getStringWidth(buf); legendFont.drawString(buf, loc.x() - sw/2, y); } - drawAirports(); - drawNavaids(); - drawPOIs(); + drawPositioned(); drawTraffic(); drawGPSData(); drawNavRadio(fgGetNode("/instrumentation/nav[0]", false)); @@ -657,8 +804,6 @@ void MapWidget::paintRuler() d->setAnchor(clickPos); d->setOffset(MapData::VALIGN_TOP | MapData::HALIGN_CENTER, 15); d->setPriority(20000); - - } void MapWidget::paintAircraftLocation(const SGGeod& aircraftPos) @@ -762,20 +907,17 @@ void MapWidget::paintRoute() void MapWidget::drawFlightHistory() { - FGFlightHistory* history = (FGFlightHistory*) globals->get_subsystem("history"); - if (!history || !_root->getBoolValue("draw-flight-history")) { + if (_flightHistoryPath.empty()) return; - } - + // first pass, draw the actual lines glLineWidth(2.0); - SGGeodVec gv(history->pathForHistory()); glColor4f(0.0, 0.0, 1.0, 0.7); glBegin(GL_LINE_STRIP); - for (unsigned int i=0; i<gv.size(); ++i) { - SGVec2d p = project(gv[i]); + for (unsigned int i=0; i<_flightHistoryPath.size(); ++i) { + SGVec2d p = project(_flightHistoryPath[i]); glVertex2d(p.x(), p.y()); } @@ -914,125 +1056,33 @@ void MapWidget::drawGPSData() } } -class MapAirportFilter : public FGAirport::AirportFilter +void MapWidget::drawPositioned() { -public: - MapAirportFilter(SGPropertyNode_ptr nd) - { - _heliports = nd->getBoolValue("show-heliports", false); - _hardRunwaysOnly = nd->getBoolValue("hard-surfaced-airports", true); - _minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 2000); - } - - virtual FGPositioned::Type maxType() const { - return _heliports ? FGPositioned::HELIPORT : FGPositioned::AIRPORT; - } - - virtual bool passAirport(FGAirport* aApt) const { - if (_hardRunwaysOnly) { - return aApt->hasHardRunwayOfLengthFt(_minLengthFt); - } - - return true; - } - - void showAll() - { - _hardRunwaysOnly = false; - } - -private: - bool _heliports; - bool _hardRunwaysOnly; - double _minLengthFt; -}; - -void MapWidget::drawAirports() -{ - MapAirportFilter af(_root); - if (_cachedZoom <= SHOW_DETAIL2_ZOOM) { - // show all airports when zoomed in sufficently - af.showAll(); - } - - bool partial = false; - FGPositionedList apts = FGPositioned::findWithinRangePartial(_projectionCenter, _drawRangeNm, &af, partial); - for (unsigned int i=0; i<apts.size(); ++i) { - drawAirport((FGAirport*) apts[i].get()); - } -} - -class NavaidFilter : public FGPositioned::Filter -{ -public: - NavaidFilter(bool fixesEnabled, bool navaidsEnabled) : - _fixes(fixesEnabled), - _navaids(navaidsEnabled) - {} - - virtual bool pass(FGPositioned* aPos) const { - if (_fixes && (aPos->type() == FGPositioned::FIX)) { - // ignore fixes which end in digits - expirmental - if (aPos->ident().length() > 4 && isdigit(aPos->ident()[3]) && isdigit(aPos->ident()[4])) { - return false; - } - } - - return true; - } - - virtual FGPositioned::Type minType() const { - return _fixes ? FGPositioned::FIX : FGPositioned::NDB; - } - - virtual FGPositioned::Type maxType() const { - return _navaids ? FGPositioned::VOR : FGPositioned::FIX; - } - -private: - bool _fixes, _navaids; -}; - -void MapWidget::drawNavaids() -{ - bool fixes = _root->getBoolValue("draw-fixes"); - NavaidFilter f(fixes, _root->getBoolValue("draw-navaids")); - - if (f.minType() <= f.maxType()) { - FGPositionedList navs = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f); - - glLineWidth(1.0); - for (unsigned int i=0; i<navs.size(); ++i) { - FGPositioned::Type ty = navs[i]->type(); - if (ty == FGPositioned::NDB) { - drawNDB(false, (FGNavRecord*) navs[i].get()); - } else if (ty == FGPositioned::VOR) { - drawVOR(false, (FGNavRecord*) navs[i].get()); - } else if (ty == FGPositioned::FIX) { - drawFix((FGFix*) navs[i].get()); - } - } // of navaid iteration - } // of navaids || fixes are drawn test -} - -void MapWidget::drawPOIs() -{ - FGPositioned::TypeFilter f(FGPositioned::COUNTRY); - f.addType(FGPositioned::CITY); - f.addType(FGPositioned::TOWN); - FGPositionedList poi = FGPositioned::findWithinRange(_projectionCenter, _drawRangeNm, &f); - - glLineWidth(1.0); - for (unsigned int i=0; i<poi.size(); ++i) { - FGPositioned::Type ty = poi[i]->type(); - if (ty == FGPositioned::COUNTRY) { - drawCountries((FGNavRecord*) poi[i].get()); - } else if (ty == FGPositioned::CITY) { - drawCities((FGNavRecord*) poi[i].get()); - } else if (ty == FGPositioned::TOWN) { - drawTowns((FGNavRecord*) poi[i].get()); - } - } // of navaid iteration + for (unsigned int i=0; i<_itemsToDraw.size(); ++i) { + FGPositionedRef p = _itemsToDraw[i]; + switch (p->type()) { + case FGPositioned::AIRPORT: + drawAirport((FGAirport*) p.get()); + break; + case FGPositioned::NDB: + drawNDB(false, (FGNavRecord*) p.get()); + break; + case FGPositioned::VOR: + drawVOR(false, (FGNavRecord*) p.get()); + break; + case FGPositioned::FIX: + drawFix((FGFix*) p.get()); + break; + case FGPositioned::TOWN: + case FGPositioned::CITY: + case FGPositioned::COUNTRY: + drawPOI(p); + break; + + default: + SG_LOG(SG_GENERAL, SG_WARN, "unhandled type in MapWidget::drawPositioned"); + } // of positioned type switch + } // of items to draw iteration } void MapWidget::drawNDB(bool tuned, FGNavRecord* ndb) @@ -1193,91 +1243,38 @@ void MapWidget::drawTunedLocalizer(SGPropertyNode_ptr radio) } } -void MapWidget::drawCountries(FGNavRecord* rec) +void MapWidget::drawPOI(FGPositioned* poi) { - if (_cachedZoom < 9) { - return; // hide labels beyond a certain zoom level - } - - SGVec2d pos = project(rec->geod()); + SGVec2d pos = project(poi->geod()); glColor3f(1.0, 1.0, 0.0); + glLineWidth(1.0); - circleAt(pos, 4, 10); + int radius = 10; + if (poi->type() == FGPositioned::CITY) { + radius = 8; + glColor3f(0.0, 1.0, 0.0); + } else if (poi->type() == FGPositioned::TOWN) { + radius = 5; + glColor3f(0.2, 1.0, 0.0); + } + + circleAt(pos, 4, radius); - if (validDataForKey(rec)) { - setAnchorForKey(rec, pos); + if (validDataForKey(poi)) { + setAnchorForKey(poi, pos); return; } char buffer[1024]; ::snprintf(buffer, 1024, "%s", - rec->name().c_str()); + poi->name().c_str()); - MapData* d = createDataForKey(rec); + MapData* d = createDataForKey(poi); d->setPriority(200); - d->setLabel(rec->ident()); + d->setLabel(poi->ident()); d->setText(buffer); d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10); d->setAnchor(pos); - -} - -void MapWidget::drawCities(FGNavRecord* rec) -{ - if (_cachedZoom > SHOW_DETAIL_ZOOM) { - return; // hide labels beyond a certain zoom level - } - - SGVec2d pos = project(rec->geod()); - glColor3f(0.0, 1.0, 0.0); - - circleAt(pos, 4, 8); - - if (validDataForKey(rec)) { - setAnchorForKey(rec, pos); - return; - } - - char buffer[1024]; - ::snprintf(buffer, 1024, "%s", - rec->name().c_str()); - - MapData* d = createDataForKey(rec); - d->setPriority(40); - d->setLabel(rec->ident()); - d->setText(buffer); - d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10); - d->setAnchor(pos); - -} - -void MapWidget::drawTowns(FGNavRecord* rec) -{ - if (_cachedZoom > SHOW_DETAIL2_ZOOM) { - return; // hide labels beyond a certain zoom level - } - - SGVec2d pos = project(rec->geod()); - glColor3f(0.2, 1.0, 0.0); - - circleAt(pos, 4, 5); - - if (validDataForKey(rec)) { - setAnchorForKey(rec, pos); - return; - } - - char buffer[1024]; - ::snprintf(buffer, 1024, "%s", - rec->name().c_str()); - - MapData* d = createDataForKey(rec); - d->setPriority(40); - d->setLabel(rec->ident()); - d->setText(buffer); - d->setOffset(MapData::HALIGN_CENTER | MapData::VALIGN_BOTTOM, 10); - d->setAnchor(pos); - } /* @@ -1491,42 +1488,10 @@ void MapWidget::drawILS(bool tuned, FGRunway* rwy) void MapWidget::drawTraffic() { - if (!_root->getBoolValue("draw-traffic")) { - return; - } - - if (_cachedZoom > SHOW_DETAIL_ZOOM) { - return; - } - - const SGPropertyNode* ai = fgGetNode("/ai/models", true); - - for (int i = 0; i < ai->nChildren(); ++i) { - const SGPropertyNode *model = ai->getChild(i); - // skip bad or dead entries - if (!model || model->getIntValue("id", -1) == -1) { - continue; + AIDrawVec::const_iterator it; + for (it = _aiDrawVec.begin(); it != _aiDrawVec.end(); ++it) { + drawAI(*it); } - - const std::string& name(model->getName()); - SGGeod pos = SGGeod::fromDegFt( - model->getDoubleValue("position/longitude-deg"), - model->getDoubleValue("position/latitude-deg"), - model->getDoubleValue("position/altitude-ft")); - - double dist = SGGeodesy::distanceNm(_projectionCenter, pos); - if (dist > _drawRangeNm) { - continue; - } - - double heading = model->getDoubleValue("orientation/true-heading-deg"); - if ((name == "aircraft") || (name == "multiplayer") || - (name == "wingman") || (name == "tanker")) { - drawAIAircraft(model, pos, heading); - } else if ((name == "ship") || (name == "carrier") || (name == "escort")) { - drawAIShip(model, pos, heading); - } - } // of ai/models iteration } void MapWidget::drawHelipad(FGHelipad* hp) @@ -1555,105 +1520,36 @@ void MapWidget::drawHelipad(FGHelipad* hp) d->setAnchor(pos); } -void MapWidget::drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg) +void MapWidget::drawAI(const DrawAIObject& dai) { + SGVec2d p = project(dai.pos); - SGVec2d p = project(pos); + if (dai.boat) { + glColor3f(0.0, 0.0, 0.5); - glColor3f(0.0, 0.0, 0.0); + } else { + glColor3f(0.0, 0.0, 0.0); + } glLineWidth(2.0); circleAt(p, 4, 6.0); // black diamond // draw heading vector - int speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt")); - if (speedKts > 1) { + if (dai.speedKts > 1) { glLineWidth(1.0); - const double dt = 15.0 / (3600.0); // 15 seconds look-ahead - double distanceM = speedKts * SG_NM_TO_METER * dt; - - SGGeod advance; - double az2; - SGGeodesy::direct(pos, hdg, distanceM, advance, az2); - + double distanceM = dai.speedKts * SG_NM_TO_METER * dt; + SGGeod advance = SGGeodesy::direct(dai.pos, dai.heading, distanceM); drawLine(p, project(advance)); } - // try to access the flight-plan of the aircraft. There are several layers - // of potential NULL-ness here, so we have to be defensive at each stage. - std::string originICAO, destinationICAO; - FGAIManager* aiManager = static_cast<FGAIManager*>(globals->get_subsystem("ai-model")); - FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL; - if (aircraft) { - FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get()); - if (p->GetFlightPlan()) { - if (p->GetFlightPlan()->departureAirport()) { - originICAO = p->GetFlightPlan()->departureAirport()->ident(); - } - - if (p->GetFlightPlan()->arrivalAirport()) { - destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident(); - } - } // of flight-plan exists - } // of check for AIBase-derived instance - - // draw callsign / altitude / speed - int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50; - std::ostringstream ss; - ss << model->getStringValue("callsign", "<>"); - if (speedKts > 1) { - ss << "\n" << altFt50 << "' " << speedKts << "kts"; - } - - if (!originICAO.empty() || ! destinationICAO.empty()) { - ss << "\n" << originICAO << " -> " << destinationICAO; - } - - MapData* d = getOrCreateDataForKey((void*) model); - d->setText(ss.str().c_str()); - d->setLabel(model->getStringValue("callsign", "<>")); - d->setPriority(speedKts > 5 ? 60 : 10); // low priority for parked aircraft + MapData* d = getOrCreateDataForKey((void*) dai.model); + d->setText(dai.legend); + d->setLabel(dai.label); + d->setPriority(dai.speedKts > 5 ? 60 : 10); // low priority for parked aircraft d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10); d->setAnchor(p); } -void MapWidget::drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg) -{ - SGVec2d p = project(pos); - - glColor3f(0.0, 0.0, 0.5); - glLineWidth(2.0); - circleAt(p, 4, 6.0); // blue diamond (to differentiate from aircraft. - -// draw heading vector - int speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts")); - if (speedKts > 1) { - glLineWidth(1.0); - - const double dt = 15.0 / (3600.0); // 15 seconds look-ahead - double distanceM = speedKts * SG_NM_TO_METER * dt; - - SGGeod advance; - double az2; - SGGeodesy::direct(pos, hdg, distanceM, advance, az2); - - drawLine(p, project(advance)); - } - - // draw callsign / speed - char buffer[1024]; - ::snprintf(buffer, 1024, "%s\n%dkts", - model->getStringValue("name", "<>"), - speedKts); - - MapData* d = getOrCreateDataForKey((void*) model); - d->setText(buffer); - d->setLabel(model->getStringValue("name", "<>")); - d->setPriority(speedKts > 2 ? 30 : 10); // low priority for slow moving ships - d->setOffset(MapData::VALIGN_CENTER | MapData::HALIGN_LEFT, 10); - d->setAnchor(p); -} - SGVec2d MapWidget::project(const SGGeod& geod) const { SGVec2d p; @@ -1979,3 +1875,63 @@ int MapWidget::displayHeading(double h) const SG_NORMALIZE_RANGE(h, 0.0, 360.0); return SGMiscd::roundToInt(h); } + +MapWidget::DrawAIObject::DrawAIObject(SGPropertyNode* m, const SGGeod& g) : + model(m), + boat(false), + pos(g), + speedKts(0) +{ + std::string name(model->getNameString()); + heading = model->getDoubleValue("orientation/true-heading-deg"); + + if ((name == "aircraft") || (name == "multiplayer") || + (name == "wingman") || (name == "tanker")) + { + speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt")); + label = model->getStringValue("callsign", "<>"); + + // try to access the flight-plan of the aircraft. There are several layers + // of potential NULL-ness here, so we have to be defensive at each stage. + std::string originICAO, destinationICAO; + FGAIManager* aiManager = static_cast<FGAIManager*>(globals->get_subsystem("ai-model")); + FGAIBasePtr aircraft = aiManager ? aiManager->getObjectFromProperty(model) : NULL; + if (aircraft) { + FGAIAircraft* p = static_cast<FGAIAircraft*>(aircraft.get()); + if (p->GetFlightPlan()) { + if (p->GetFlightPlan()->departureAirport()) { + originICAO = p->GetFlightPlan()->departureAirport()->ident(); + } + + if (p->GetFlightPlan()->arrivalAirport()) { + destinationICAO = p->GetFlightPlan()->arrivalAirport()->ident(); + } + } // of flight-plan exists + } // of check for AIBase-derived instance + + // draw callsign / altitude / speed + int altFt50 = static_cast<int>(pos.getElevationFt() / 50.0) * 50; + std::ostringstream ss; + ss << model->getStringValue("callsign", "<>"); + if (speedKts > 1) { + ss << "\n" << altFt50 << "' " << speedKts << "kts"; + } + + if (!originICAO.empty() || ! destinationICAO.empty()) { + ss << "\n" << originICAO << " -> " << destinationICAO; + } + + legend = ss.str(); + } else if ((name == "ship") || (name == "carrier") || (name == "escort")) { + boat = true; + speedKts = static_cast<int>(model->getDoubleValue("velocities/speed-kts")); + label = model->getStringValue("name", "<>"); + + char buffer[1024]; + ::snprintf(buffer, 1024, "%s\n%dkts", + model->getStringValue("name", "<>"), + speedKts); + legend = buffer; + } +} + diff --git a/src/GUI/MapWidget.hxx b/src/GUI/MapWidget.hxx index 0a826f761..1ad5aa15a 100644 --- a/src/GUI/MapWidget.hxx +++ b/src/GUI/MapWidget.hxx @@ -6,9 +6,10 @@ #include <simgear/math/SGMath.hxx> #include <simgear/props/props.hxx> +#include <Navaids/positioned.hxx> #include <plib/pu.h> -#include "dialog.hxx" // for GUI_ID +#include "FGPUIDialog.hxx" // forward decls class FGRouteMgr; @@ -20,18 +21,25 @@ class FGFix; class MapData; class SGMagVar; -class MapWidget : public puObject +typedef std::vector<SGGeod> SGGeodVec; + +class MapWidget : public puObject, public FGPUIDialog::ActiveWidget { public: MapWidget(int x, int y, int width, int height); virtual ~MapWidget(); + // puObject over-rides virtual void setSize(int width, int height); virtual void doHit( int button, int updown, int x, int y ) ; virtual void draw( int dx, int dy ) ; virtual int checkKey(int key, int updown); void setProperty(SGPropertyNode_ptr prop); + + // PUIDialog::ActiveWidget over-rides + virtual void update(); + private: enum Projection { PROJECTION_SAMSON_FLAMSTEED, @@ -60,7 +68,6 @@ private: SGVec2d gridPoint(int ix, int iy); bool drawLineClipped(const SGVec2d& a, const SGVec2d& b); - void drawAirports(); void drawAirport(FGAirport* apt); int scoreAirportRunways(FGAirport* apt); void drawRunwayPre(FGRunway* rwy); @@ -68,19 +75,34 @@ private: void drawHelipad(FGHelipad* hp); void drawILS(bool tuned, FGRunway* rwy); - void drawNavaids(); - void drawPOIs(); + void drawPositioned(); void drawNDB(bool tuned, FGNavRecord* nav); void drawVOR(bool tuned, FGNavRecord* nav); void drawFix(FGFix* fix); - void drawCountries(FGNavRecord* rec); - void drawCities(FGNavRecord* rec); - void drawTowns(FGNavRecord* rec); - + void drawPOI(FGPositioned* rec); + void drawTraffic(); - void drawAIAircraft(const SGPropertyNode* model, const SGGeod& pos, double hdg); - void drawAIShip(const SGPropertyNode* model, const SGGeod& pos, double hdg); + + class DrawAIObject + { + public: + DrawAIObject(SGPropertyNode* model, const SGGeod& g); + + SGPropertyNode* model; + bool boat; + SGGeod pos; + double heading; + int speedKts; + std::string label; + std::string legend; + }; + + typedef std::vector<DrawAIObject> AIDrawVec; + AIDrawVec _aiDrawVec; + + void updateAIObjects(); + void drawAI(const DrawAIObject& dai); void drawData(); bool validDataForKey(void* key); @@ -107,7 +129,9 @@ private: double _upHeading; // true heading corresponding to +ve y-axis bool _magneticHeadings; bool _hasPanned; - + bool _aircraftUp; + int _displayHeading; + SGGeod _projectionCenter; Projection _projection; SGGeod _aircraft; @@ -116,7 +140,10 @@ private: FGRouteMgr* _route; SGPropertyNode_ptr _root; SGPropertyNode_ptr _gps; - + SGGeodVec _flightHistoryPath; + + FGPositionedList _itemsToDraw; + typedef std::map<void*, MapData*> KeyDataMap; KeyDataMap _mapData; std::vector<MapData*> _dataQueue;