From 527fa8a492298c227243d78219038b4c0885aac4 Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Mon, 14 Jul 2014 23:31:16 +0100 Subject: [PATCH] refs #296 implemented airportsInRange on XBus side --- src/blackmisc/geoallclasses.h | 1 + src/blackmisc/geodesicgrid.h | 177 ++++++++++++++++++++++++++++++++++ src/blackmisc/iterator.h | 58 +++++++++++ src/xbus/main.cpp | 14 ++- src/xbus/plugin.cpp | 8 ++ src/xbus/plugin.h | 3 + src/xbus/service.cpp | 62 ++++++++++++ src/xbus/service.h | 21 ++++ 8 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/blackmisc/geodesicgrid.h diff --git a/src/blackmisc/geoallclasses.h b/src/blackmisc/geoallclasses.h index a8b82f0e9..fec0a2b14 100644 --- a/src/blackmisc/geoallclasses.h +++ b/src/blackmisc/geoallclasses.h @@ -9,6 +9,7 @@ #include "blackmisc/geoearthangle.h" #include "blackmisc/geolatitude.h" #include "blackmisc/geolongitude.h" +#include "blackmisc/geodesicgrid.h" #include "blackmisc/coordinateecef.h" #include "blackmisc/coordinatened.h" #include "blackmisc/coordinategeodetic.h" diff --git a/src/blackmisc/geodesicgrid.h b/src/blackmisc/geodesicgrid.h new file mode 100644 index 000000000..d7f1020ed --- /dev/null +++ b/src/blackmisc/geodesicgrid.h @@ -0,0 +1,177 @@ +/* Copyright (C) 2013 VATSIM Community / contributors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BLACKMISC_GEODESICGRID_H +#define BLACKMISC_GEODESICGRID_H + +//! \file + +#include "mathematics.h" +#include "coordinategeodetic.h" +#include "range.h" +#include "iterator.h" +#include +#include + +namespace BlackMisc +{ + namespace Geo + { + + // Compute the integer log2(X) of an integer X at compile time + //! \private + template + struct LogBase2 : std::conditional> 1), + LogBase2> 1), Count + 1>, + std::integral_constant>::type + {}; + + //! Associative container for efficiently storing and retreiving elements at points on the Earth's surface. + /* + We treat the Earth as a unit sphere, and convert latitude/longitude coordinates into x,y,z normal vectors. + The x axis points to 00N00W, the y axis points to 00N90E, the z axis points to the north pole. + + We slice the Earth into N slices along lines of latitude (i.e. planes perpendicular to the z axis). Call these the z slices. + Then we further slice the Earth along planes perpendicular to the x axis and the y axis. Call these the x slices and the y slices. + Where an x slice, a y slice, and a z slice intersect on the sphere, there is a triangular tile of height 21600/2^N nautical miles. + */ + template + class CGeodesicGrid + { + public: + //! Iterator + typedef typename QMultiMap::const_iterator const_iterator; + + //! Begin and end iterators of the underlying storage. + //! @{ + const_iterator begin() const { return m_map.begin(); } + const_iterator cbegin() const { return m_map.cbegin(); } + const_iterator end() const { return m_map.end(); } + const_iterator cend() const { return m_map.cend(); } + //! @} + + //! Removes all elements from all tiles. + void clear() { m_map.clear(); } + + //! Returns true if there are no elements in any tiles. + bool isEmpty() const { return m_map.isEmpty(); } + + //! Inserts an element in the tile at the given point. + //! \warning Angles are in radians. + void insert(double lat, double lon, const T &value) { m_map.insert(coordinateToKey(lat, lon), value); } + + //! If T has latitude() and longitude() methods then this convenience insert() method can be used. + void insert(const T &value) { m_map.insert(value.latitude(), value.longitude(), value); } + + //! Returns a range containing the elements in the tile at the given point. + //! \warning Angles are in radians. + CRange inTileAt(double lat, double lon) const + { + Key k = coordinateToKey(lat, lon); + return makeRange(m_map.lowerBound(k), m_map.upperBound(k)); + } + + //! Returns a range containing the elements in every tile adjacent to the one at the given point, including that one. + //! \warning Angles are in radians. + CRange> inAdjacentTiles(double lat, double lon, int degree = 1) const + { + QVector its; + for (auto k : adjacentKeys(coordinateToKey(lat, lon), degree)) + { + its.push_back(m_map.lowerBound(k)); + its.push_back(m_map.upperBound(k)); + } + Q_ASSERT(!its.isEmpty()); + return makeRange(Iterators::makeConcatIterator(its), its.back()); + } + + //! Overloaded method taking the coordinates in a different form. + //! @{ + void insert(const CLatitude &lat, const CLongitude &lon, const T &value) { insert(lat.value(PhysicalQuantities::CAngleUnit::rad()), lon.value(PhysicalQuantities::CAngleUnit::rad()), value); } + void insert(const ICoordinateGeodetic &coord, const T &value) { insert(coord.latitude(), coord.longitude()); } + CRange inTileAt(const CLatitude &lat, const CLongitude &lon) const { return inTileAt(lat.value(PhysicalQuantities::CAngleUnit::rad()), lon.value(PhysicalQuantities::CAngleUnit::rad())); } + CRange inTileAt(const ICoordinateGeodetic &coord) const { return inTileAt(coord.latitude(), coord.longitude()); } + CRange> inAdjacentTiles(const CLatitude &lat, const CLongitude &lon, int degree = 1) const { return inAdjacentTiles(lat.value(PhysicalQuantities::CAngleUnit::rad()), lon.value(PhysicalQuantities::CAngleUnit::rad()), degree); } + CRange> inAdjacentTiles(const ICoordinateGeodetic &coord, int degree = 1) const { return inAdjacentTiles(coord.latitude(), coord.longitude(), degree); } + //! @} + + //! Returns the internal keys corresponding to all the tiles. + QList keys() const { return m_map.uniqueKeys(); } + + //! Returns the number of elements in the tile corresponding to this internal key. + int count(Key k) const { return m_map.count(k); } + + private: + QMultiMap m_map; + + static_assert(std::is_signed::value && std::is_integral::value, "Key must be a signed integer"); + static_assert(Slices > 1 && !(Slices & (Slices - 1)), "Slices must be a power of two"); + static_assert(LogBase2::value * 3 < sizeof(Key) * 8, "Key is too small to hold all Slices"); + + static const Key Zshift = 0; + static const Key Zmask = Slices - 1; + static const Key Zone = 1; + static const Key Yshift = LogBase2::value; + static const Key Ymask = Zmask << Yshift; + static const Key Yone = Zone << Yshift; + static const Key Xshift = Yshift * 2; + static const Key Xmask = Zmask << Xshift; + static const Key Xone = Zone << Xshift; + + static Key coordinateToKey(double lat, double lon) + { + using namespace std; + using namespace BlackMisc::Math; + Q_ASSERT(lat >= -CMath::PIHALF() && lat <= CMath::PIHALF()); + Q_ASSERT(lon >= -CMath::PI() && lon <= CMath::PI()); + static const double ratio = Slices / CMath::PI(); + Key x = qFloor(acos(cos(lat) * cos(lon)) * ratio); + Key y = qFloor(acos(cos(lat) * sin(lon)) * ratio); + Key z = qFloor( (lat + CMath::PIHALF()) * ratio); + return (x << Xshift) | (y << Yshift) | (z << Zshift); + } + + static QVector adjacentKeys(Key k, int d) + { + QVector adj; + for (int dx = -d; dx <= d; ++dx) + { + for (int dy = -d; dy <= d; ++dy) + { + for (int dz = -d; dz <= d; ++dz) + { + adj.push_back(plus(k, dx, dy, dz)); + } + } + } + return adj; + } + + static Key plus(Key k, Key dx, Key dy, Key dz) + { + Key x = k & Xmask; + Key y = k & Ymask; + Key z = k & Zmask; + dx *= Xone; + dy *= Yone; + dz *= Zone; + if ((dx < 0 ? (-dx > x) : (dx > Xmask - x)) + || (dy < 0 ? (-dy > y) : (dy > Ymask - y)) + || (dz < 0 ? (-dz > z) : (dz > Zmask - z))) + { + return -1; + } + else + { + return (x + dx) | (y + dy) | (z + dz); + } + } + }; + + } // namespace +} // namespace + +#endif // guard + diff --git a/src/blackmisc/iterator.h b/src/blackmisc/iterator.h index d09965934..123fbf78f 100644 --- a/src/blackmisc/iterator.h +++ b/src/blackmisc/iterator.h @@ -204,6 +204,56 @@ namespace BlackMisc Optional m_predicate; }; + /*! + * Iterator wrapper which concatenates zero or more pairs of begin and end iterators. + */ + template class ConcatIterator : public std::iterator::value_type> + { + public: + //! Constructor. + ConcatIterator(QVector iterators) : m_iterators(std::move(iterators)) + { + Q_ASSERT(m_iterators.size() % 2 == 0); + while (!m_iterators.empty() && m_iterators[0] == m_iterators[1]) { m_iterators.remove(0, 2); } + } + + //! Implicit conversion from an end iterator. + ConcatIterator(I end) { Q_UNUSED(end); } + + //! Advance to the next element. + //! Undefined if iterator is at the end. + //! @{ + ConcatIterator &operator ++() + { + ++(m_iterators[0]); + while (!m_iterators.empty() && m_iterators[0] == m_iterators[1]) { m_iterators.remove(0, 2); } + return *this; + } + ConcatIterator operator ++(int) { auto copy = *this; ++(*this); return copy; } + //! @} + + //! Indirection operator, returns the underlying iterator. + //! Undefined if iterator is at the end. + I operator ->() { return m_iterators[0]; } + + //! Dereference operator, returns the object referenced by the iterator. + //! Undefined if iterator is at the end. + typename std::iterator_traits::reference operator *() { return *(m_iterators[0]); } + + //! Comparison operators. + //! @{ + bool operator ==(const ConcatIterator &other) const { return m_iterators == other.m_iterators; } + bool operator !=(const ConcatIterator &other) const { return m_iterators != other.m_iterators; } + bool operator <(const ConcatIterator &other) const { return m_iterators < other.m_iterators; } + bool operator <=(const ConcatIterator &other) const { return m_iterators <= other.m_iterators; } + bool operator >(const ConcatIterator &other) const { return m_iterators > other.m_iterators; } + bool operator >=(const ConcatIterator &other) const { return m_iterators >= other.m_iterators; } + //! @} + + private: + QVector m_iterators; + }; + /*! * Construct a KeyIterator of the appropriate type from deduced template function argument. */ @@ -228,6 +278,14 @@ namespace BlackMisc return { iterator, end, predicate }; } + /*! + * Construct a ConcatIterator of the appropriate type from deduced template function arguments. + */ + template auto makeConcatIterator(QVector iterators) -> ConcatIterator + { + return { std::move(iterators) }; + } + /*! * \brief Generic type-erased const forward iterator with value semantics. * \tparam T the value_type of the container being iterated over. diff --git a/src/xbus/main.cpp b/src/xbus/main.cpp index 30b70dfdd..e84619c15 100644 --- a/src/xbus/main.cpp +++ b/src/xbus/main.cpp @@ -7,6 +7,7 @@ #include "plugin.h" #include "utils.h" #include "traffic.h" +#include "service.h" #include #if ! defined(XPLM210) @@ -32,9 +33,13 @@ PLUGIN_API void XPluginStop() PLUGIN_API int XPluginEnable() { + qRegisterMetaType(); + qDBusRegisterMetaType(); + QXPlaneMessageHandler::install(); g_qApp = QSharedApplication::sharedInstance(); QXPlaneEventLoop::exec(); + g_plugin = new XBus::CPlugin; return 1; } @@ -49,12 +54,19 @@ PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, long msg, void *param) { if (from == XPLM_PLUGIN_XPLANE) { - if (msg == XPLM_MSG_PLANE_LOADED || msg == XPLM_MSG_LIVERY_LOADED) + switch (msg) { + case XPLM_MSG_PLANE_LOADED: + case XPLM_MSG_LIVERY_LOADED: if (reinterpret_cast(param) == XPLM_USER_AIRCRAFT) { g_plugin->onAircraftModelChanged(); } + break; + + case XPLM_MSG_AIRPORT_LOADED: + g_plugin->onAircraftRepositioned(); + break; } } } diff --git a/src/xbus/plugin.cpp b/src/xbus/plugin.cpp index dd91c25dc..2d3eb9c61 100644 --- a/src/xbus/plugin.cpp +++ b/src/xbus/plugin.cpp @@ -40,4 +40,12 @@ namespace XBus } } + void CPlugin::onAircraftRepositioned() + { + if (m_service) + { + m_service->updateAirportsInRange(); + } + } + } diff --git a/src/xbus/plugin.h b/src/xbus/plugin.h index 72f8c6b30..7341bc36e 100644 --- a/src/xbus/plugin.h +++ b/src/xbus/plugin.h @@ -42,6 +42,9 @@ namespace XBus //! Called by XPluginReceiveMessage when the model is changed void onAircraftModelChanged(); + //! Called by XPluginReceiveMessage when the aircraft is positioned at an airport + void onAircraftRepositioned(); + private: BlackCore::CDBusServer *m_server = nullptr; CService *m_service = nullptr; diff --git a/src/xbus/service.cpp b/src/xbus/service.cpp index 695e5d804..b61dd018e 100644 --- a/src/xbus/service.cpp +++ b/src/xbus/service.cpp @@ -6,12 +6,18 @@ #include "service.h" #include #include +#include +#include namespace XBus { CService::CService(QObject *parent) : QObject(parent) { + m_airportUpdater = new QTimer(this); + m_airportUpdater->start(60000); + connect(m_airportUpdater, &QTimer::timeout, this, &CService::updateAirportsInRange); + updateAirportsInRange(); } void CService::onAircraftModelChanged() @@ -68,4 +74,60 @@ namespace XBus return path; } + void CService::readAirportsDatabase() + { + auto first = XPLMFindFirstNavAidOfType(xplm_Nav_Airport); + auto last = XPLMFindLastNavAidOfType(xplm_Nav_Airport); + if (first != XPLM_NAV_NOT_FOUND) + { + for (auto i = first; i <= last; ++i) + { + float lat, lon; + char icao[32]; + XPLMGetNavAidInfo(i, nullptr, &lat, &lon, nullptr, nullptr, nullptr, icao, nullptr, nullptr); + if (icao[0] != 0) + { + using namespace BlackMisc::Math; + m_airports.insert(CMath::deg2rad(lat), CMath::deg2rad(lon), i); + } + } + } + + int total = 0, count = 0, max = 0; + for (auto key : m_airports.keys()) + { + qDebug() << "<><><><>" << QString("%1").arg(key, 6, 16, QChar('0')) << m_airports.count(key); + total += m_airports.count(key); + count++; + if (m_airports.count(key) > max) { max = m_airports.count(key); } + } + qDebug() << "<><><><> total" << total; + qDebug() << "<><><><> max" << max; + qDebug() << "<><><><> mean" << (total / count); + } + + void CService::updateAirportsInRange() + { + if (m_airports.isEmpty()) + { + readAirportsDatabase(); + } + using namespace BlackMisc::Math; + QStringList icaos, names; + QDoubleList lats, lons, alts; + for (auto navref : m_airports.inAdjacentTiles(CMath::deg2rad(getLatitude()), CMath::deg2rad(getLongitude()))) + { + float lat, lon, alt; + char icao[32], name[256]; + XPLMGetNavAidInfo(navref, nullptr, &lat, &lon, &alt, nullptr, nullptr, icao, name, nullptr); + icaos.push_back(icao); + names.push_back(name); + lats.push_back(lat); + lons.push_back(lon); + alts.push_back(alt); + } + qDebug() << alts; + emit airportsInRangeUpdated(icaos, names, lats, lons, alts); + } + } diff --git a/src/xbus/service.h b/src/xbus/service.h index 712058e10..e15316635 100644 --- a/src/xbus/service.h +++ b/src/xbus/service.h @@ -8,14 +8,25 @@ //! \file +#define NOMINMAX #include "datarefs.h" +#include "blackmisc/geodesicgrid.h" +#include +#include #include +#include + +class QTimer; //! \cond PRIVATE #define XBUS_SERVICE_INTERFACENAME "net.vatsim.xbus.service" #define XBUS_SERVICE_OBJECTPATH "/xbus" //! \endcond +//! Typedef needed to use QList as a DBus argument +typedef QList QDoubleList; +Q_DECLARE_METATYPE(QDoubleList); + namespace XBus { @@ -52,7 +63,13 @@ namespace XBus //! Emitted when the model or livery changes. void aircraftModelChanged(const QString &path, const QString &filename, const QString &livery, const QString &icao); + //! Airports in range updated. + void airportsInRangeUpdated(const QStringList &icaoCodes, const QStringList &names, const QDoubleList &lats, const QDoubleList &lons, const QDoubleList &alts); + public slots: + //! Called by newly connected client to cause airportsInRangeUpdated to be emitted. + void updateAirportsInRange(); + //! Get full path to current aircraft model QString getAircraftModelPath() const; @@ -153,6 +170,10 @@ namespace XBus void setTransponderMode(int mode) { m_xpdrMode.set(mode); } private: + BlackMisc::Geo::CGeodesicGrid<128, XPLMNavRef> m_airports; + QTimer *m_airportUpdater = nullptr; + void readAirportsDatabase(); + StringDataRef m_liveryPath; StringDataRef m_icao; DataRef m_latitude;