Utilisation de la carte QML pour créer des voies aériennes - Partie 1

Depuis un certain temps maintenant, j'utilise QML pour créer des interfaces graphiques, mais jusqu'à présent, il n'y a eu aucune possibilité de travailler dans un vrai projet avec l' API Qt Location et la carte QML.
Par conséquent, il est devenu intéressant d'essayer ce composant pour la construction de voies respiratoires.
Sous le cutter se trouve la description de l'implémentation de l'éditeur, pour créer des chemins similaires sur la carte:

image

Pour simplifier l'implémentation, nos avions volent dans le plan 2D à la même hauteur. La vitesse et la surcharge admissible sont fixes - 920 km / h et 3 g, ce qui donne un rayon de braquage


La trajectoire se compose des segments suivants:
image
où S est le début de la manœuvre (c'est le point de sortie du précédent), M est le début du virage, E est la sortie de celle-ci et F est le point final (M pour le suivant).

Pour calculer le point d'entrée et de sortie de la trajectoire, j'ai utilisé l' équation d'une tangente à un cercle, les calculs se sont révélés assez lourds, je suis sûr que cela peut être simplifié.

void Manoeuvre::calculate() { // General equation of line between first and middle points auto A = mStart.y() - mMiddle.y(); auto B = mMiddle.x() - mStart.x(); // Check cross product sign whether final point lies on left side auto crossProduct = (B*(mFinal.y() - mStart.y()) + A*(mFinal.x() - mStart.x())); // All three points lie on the same line if (isEqualToZero(crossProduct)) { mIsValid = true; mCircle = mExit = mMiddle; return; } mIsLeftTurn = crossProduct > 0; auto lineNorm = A*A + B*B; auto exitSign = mIsLeftTurn ? 1 : -1; auto projection = exitSign*mRadius * qSqrt(lineNorm); // Center lies on perpendicular to middle point if (!isEqualToZero(A) && !isEqualToZero(B)) { auto C = -B*mStart.y() - A*mStart.x(); auto right = (projection - C)/A - (mMiddle.x()*lineNorm + A*C) / (B*B); mCircle.ry() = right / (A/B + B/A); mCircle.rx() = (projection - B*mCircle.y() - C) / A; } else { // Entering line is perpendicular to either x- or y-axis auto deltaY = isEqualToZero(A) ? 0 : exitSign*mRadius; auto deltaX = isEqualToZero(B) ? 0 : exitSign*mRadius; mCircle.ry() = mMiddle.y() + deltaY; mCircle.rx() = mMiddle.x() + deltaX; } // Check if final point is outside manouevre circle auto circleDiffX = mFinal.x() - mCircle.x(); auto circleDiffY = mFinal.y() - mCircle.y(); auto distance = qSqrt(circleDiffX*circleDiffX + circleDiffY*circleDiffY); mIsValid = distance > mRadius; // Does not make sence to calculate futher if (!mIsValid) return; // Length of hypotenuse from final point to exit point auto beta = qAtan2(mCircle.y() - mFinal.y(), mCircle.x() - mFinal.x()); auto alpha = qAsin(mRadius / distance); auto length = qSqrt(distance*distance - mRadius*mRadius); // Depends on position of final point find exit point mExit.rx() = mFinal.x() + length*qCos(beta + exitSign*alpha); mExit.ry() = mFinal.y() + length*qSin(beta + exitSign*alpha); // Finally calculate start/span angles auto startAngle = qAtan2(mCircle.y() - mMiddle.y(), mMiddle.x() - mCircle.x()); auto endAngle = qAtan2(mCircle.y() - mExit.y(), mExit.x() - mCircle.x()); mStartAngle = startAngle < 0 ? startAngle + 2*M_PI : startAngle; endAngle = endAngle < 0 ? endAngle + 2*M_PI : endAngle; auto smallSpan = qFabs(endAngle - mStartAngle); auto bigSpan = 2*M_PI - qFabs(mStartAngle - endAngle); bool isZeroCrossed = mStartAngle > endAngle; if (!mIsLeftTurn) { mSpanAngle = isZeroCrossed ? bigSpan : smallSpan; } else { mSpanAngle = isZeroCrossed ? smallSpan : bigSpan; } } 

Après avoir mal calculé le modèle mathématique de notre trajectoire, nous allons travailler directement avec la carte. Un choix naturel pour construire des polylignes sur une carte QML consiste à ajouter MapPolyline directement à la carte.

 Map { id: map plugin: Plugin { name: "osm" } MapPolyline { path: [ { latitude: -27, longitude: 153.0 }, ... ] } } 

Au départ, je voulais donner à l'utilisateur la possibilité de simuler chaque section suivante de l'itinéraire «à la volée» - pour créer un effet de la trajectoire derrière le curseur.

image

Changer le chemin lors du déplacement du curseur est une opération assez coûteuse, j'ai donc essayé d'utiliser des chemins «pixel» préliminaires qui sont affichés jusqu'à ce que l'utilisateur enregistre finalement l'itinéraire.

 Repeater { id: trajectoryView model: flightRegistry.hasActiveFlight ? flightRegistry.flightModel : [] FlightItem { anchors.fill: parent startPoint: start endPoint: end manoeuvreRect: rect manoeuvreStartAngle: startAngle manoeuvreSpanAngle: spanAngle isVirtualLink: isVirtual } } 

FlightItem est un QQuickItem , et QAbstractListModel flightModel vous permet de mettre à jour les sections nécessaires de la trajectoire lors du changement des données de manœuvre.

 QVariant FlightModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case FlightRoles::StartPoint: return mFlight->flightSegment(index.row()).line().p1(); case FlightRoles::EndPoint: return mFlight->flightSegment(index.row()).line().p2(); ... } 

Une telle mise à jour en direct vous permet d'avertir l'utilisateur de manœuvres irréalisables.

image

Ce n'est qu'après l'achèvement de la création de la voie aérienne (par exemple, avec le clic droit de la souris) que l'itinéraire sera finalement ajouté à la carte QML en tant que GeoPath avec possibilité de géoréférencement (jusqu'à ce moment, la carte ne peut pas être déplacée et zoomée, les pixels ne savent rien de la longitude et de la latitude).
Afin de recalculer un segment de pixel en géo-coordonnées, pour les débutants, nous devons utiliser un système de coordonnées local au point d'entrée de manœuvre (notre point S) pour chaque manœuvre.

 QPointF FlightGeoRoute::toPlaneCoordinate(const QGeoCoordinate &origin, const QGeoCoordinate &point) { auto distance = origin.distanceTo(point); auto azimuth = origin.azimuthTo(point); auto x = qSin(qDegreesToRadians(azimuth)) * distance; auto y = qCos(qDegreesToRadians(azimuth)) * distance; return QPointF(x, y); } 

Après avoir recalculé la manœuvre déjà en mètres, vous devez effectuer l'opération inverse et connaître la géolocalisation du point S pour traduire les mètres en latitude-longitude.

 QGeoCoordinate FlightGeoRoute::toGeoCoordinate(const QGeoCoordinate &origin, const QPointF &point) { auto distance = qSqrt(point.x()*point.x() + point.y()*point.y()); auto radianAngle = qAtan2(point.x(), point.y()); auto azimuth = qRadiansToDegrees(radianAngle < 0 ? radianAngle + 2*M_PI : radianAngle); return origin.atDistanceAndAzimuth(distance, azimuth); } 


D'un point de vue formel, il est bien sûr impossible de considérer nos trajectoires «pixel» et «en mètres» identiques, mais il m'a semblé très savoureux de regarder vers l'avenir et de montrer à l'utilisateur ce qui va se passer (ou ne se passera pas si l'avion ne vole pas comme ça) quand il cliquera la prochaine fois. Après avoir finalisé la trajectoire (elle diffère légèrement de celle du pixel en couleur et en transparence, car même les lignes brisées statiques ne semblent pas très lisses sur la carte).

image

Les sources sont disponibles ici ; pour la compilation, j'ai utilisé Qt 5.11.2.

Dans la partie suivante, nous apprendrons à notre éditeur à déplacer les points de référence de la trajectoire, ainsi qu'à enregistrer / ouvrir les itinéraires existants pour une simulation ultérieure du mouvement des avions.

Source: https://habr.com/ru/post/fr433828/


All Articles