QMLマップを使用して気道を構築する-パート1

かなり長い間、QMLを使用してグラフィカルインターフェイスを構築してきましたが、これまでのところ、 Qt Location APIとQML Mapを使用して実際のプロジェクトで作業する機会はありませんでした。

したがって、気道を構築するためにこのコンポーネントを試すことは興味深いものになりました。

カットの下には、マップ上に同様のパスを作成するためのエディターの実装の説明があります。



画像



実装を簡素化するために、プレーンは同じ高さの2Dプレーンで飛行します。 速度と許容される過負荷は固定されています-920 km / hおよび3g(回転半径を与える)





R= fracv2G=21770m







軌跡は、次のセグメントで構成されています。

画像

ここで、Sは機動の開始点(前の出口点)、Mはターンの開始点、Eはそこからの出口、Fは最終点(次の点はM)です。



軌跡の入口と出口を計算するために、円の接線の方程式を使用しましたが、計算はかなり厄介であることが判明し、より簡単にできると確信しています。



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





軌道の数学モデルの計算ミスを完了したら、マップを直接操作します。 QMLマップ上にポリラインを構築するための自然な選択は、 MapPolylineをマップに直接追加することです。



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





最初は、ルートの後続の各セクションを「オンザフライ」でシミュレートする機会をユーザーに与えたいと思いました。これは、カーソルの後ろの軌跡の動きの効果を作成するためです。



画像



カーソルを移動するときにパスを変更するのはかなり費用のかかる操作なので、ユーザーが最終的にルートを保存するまで表示される予備の「ピクセル」パスを使用してみました。



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





FlightItemQQuickItemであり、 QAbstractListModel flightModelを使用すると、操縦のためにデータを変更するときに軌道の必要なセクションを更新できます。



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





このようなライブ更新により、ユーザーに実現不可能な操作について警告することができます。



画像



エアルートの作成が完了すると(たとえば、マウスの右クリックで)、最終的にジオリファレンスとしてQMLマップにルートが追加されます(この時点で、マップを移動およびズームできない限り、ピクセルは経度と緯度について何も知りません)。

ピクセルセグメントを地理座標に再計算するために、スターターのために、各操作に操作エントリポイント(私たちのポイントS)にローカルな座標系を使用する必要があります。



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





すでにメーターを操作して再計算した後、逆の操作を行い、ポイントSのジオロケーションを把握して、メーターを緯度経度に変換する必要があります。



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







正式な観点からは、もちろん、「ピクセル」と「メートル単位」の軌跡を同一と見なすことは不可能ですが、未来を見て、何が起こるかをユーザーに示すことは非常においしいと思われました彼は次回をクリックします。 軌跡を完成させた後(静的な破線でもマップ上で非常に滑らかに見えないため、色と透明度がピクセルとわずかに異なります)。



画像



ソースはここから入手できます ;コンパイルにはQt 5.11.2を使用しました。



次のパートでは、エディターに、軌道の基準点を移動することと、航空機の移動の後続のシミュレーションのために既存のルートを保存/開くことを教えます。



All Articles