Verwenden der QML-Karte zum Erstellen von Atemwegen - Teil 1

Seit einiger Zeit verwende ich QML, um grafische Oberflächen zu erstellen, aber bisher gab es keine Möglichkeit, mit der Qt Location API und der QML Map in einem realen Projekt zu arbeiten.
Daher wurde es interessant, diese Komponente für den Bau von Atemwegen auszuprobieren.
Unter dem Cutter befindet sich die Beschreibung der Implementierung des Editors, um ähnliche Pfade auf der Karte zu erstellen:

Bild

Um die Implementierung zu vereinfachen, fliegen unsere Flugzeuge in der 2D-Ebene auf derselben Höhe. Die Geschwindigkeit und die zulässige Überlast sind fest - 920 km / h und 3 g, was einen Wenderadius ergibt

R= fracv2G=21770m


Die Flugbahn besteht aus folgenden Segmenten:
Bild
Dabei ist S der Beginn des Manövers (es ist der Ausgangspunkt des vorherigen Manövers), M der Beginn der Kurve, E der Ausgang des Manövers und F der Endpunkt (M für den nächsten).

Um den Eintritts- und Austrittspunkt aus der Flugbahn zu berechnen, habe ich die Gleichung einer Tangente an einen Kreis verwendet. Die Berechnungen erwiesen sich als ziemlich umständlich. Ich bin sicher, dass dies einfacher gemacht werden kann.

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; } } 

Nachdem wir die Fehlberechnung des mathematischen Modells unserer Flugbahn abgeschlossen haben, arbeiten wir direkt mit der Karte. Eine natürliche Wahl zum Erstellen von Polylinien auf einer QML-Karte besteht darin, MapPolyline direkt zur Karte hinzuzufügen.

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

Zunächst wollte ich dem Benutzer die Möglichkeit geben, jeden nachfolgenden Abschnitt der Route „on the fly“ zu simulieren - um den Effekt der Bewegung der Flugbahn hinter dem Cursor zu erzeugen.

Bild

Das Ändern des Pfads beim Bewegen des Cursors ist eine ziemlich teure Operation. Daher habe ich versucht, vorläufige „Pixel“ -Pfade zu verwenden, die angezeigt werden, bis der Benutzer die Route endgültig speichert.

 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 ist ein QQuickItem , und mit QAbstractListModel FlightModel können Sie die erforderlichen Abschnitte der Flugbahn aktualisieren, wenn Sie Daten für das Manöver ändern.

 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(); ... } 

Mit einem solchen Live-Update können Sie den Benutzer vor nicht realisierbaren Manövern warnen.

Bild

Erst nach Abschluss der Erstellung des Atemwegs (z. B. mit der rechten Maustaste) wird die Route endgültig als GeoPath mit der Möglichkeit der Georeferenzierung zur QML-Karte hinzugefügt (bis zu diesem Moment kann die Karte nicht verschoben und gezoomt werden, die Pixel wissen nichts über Längen- und Breitengrad).
Um ein Pixelsegment in eine Geokoordinate neu zu berechnen, müssen wir für jedes Manöver zunächst ein Koordinatensystem verwenden, das lokal zum Manövereintrittspunkt (unserem Punkt S) liegt.

 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); } 

Nachdem wir das Manöver bereits Meter neu berechnet haben, müssen Sie den umgekehrten Vorgang ausführen und die Geolokalisierung von Punkt S kennen, um die Meter in Längen- und Breitengrad zu übersetzen.

 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); } 


Aus formaler Sicht ist es natürlich unmöglich, unsere Flugbahn „Pixel“ und „in Metern“ als identisch zu betrachten, aber es schien mir sehr lecker, in die Zukunft zu schauen und dem Benutzer zu zeigen, was wann passieren wird (oder nicht passieren wird, wenn das Flugzeug nicht so fliegt) Er wird das nächste Mal klicken. Nach Abschluss der Flugbahn (sie unterscheidet sich geringfügig von der Pixelbahn in Farbe und Transparenz, da selbst statische gestrichelte Linien auf der Karte nicht sehr glatt aussehen).

Bild

Quellen sind hier verfügbar, für die Kompilierung habe ich Qt 5.11.2 verwendet.

Im nächsten Teil werden wir unserem Editor beibringen, die Referenzpunkte der Flugbahn zu verschieben sowie vorhandene Spuren für die anschließende Simulation der Bewegung von Flugzeugen zu speichern / öffnen.

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


All Articles