プレーンスポッター(QML)

Plane Spotter の例では、位置・測位データ型を QML に緊密に統合しています。

Plane Spotter の例では、位置・測位に関連する C++ データ型を QML に統合する方法と、その逆を示します。これは、CPU負荷の高い位置計算をネイティブ環境で実行し、その結果をQMLで表示したい場合に便利です。

この例では、ヨーロッパの地図と、ヨーロッパを横断する2つのルートの飛行機を示しています。最初の飛行機はオスロとベルリンを、2番目の飛行機はロンドンとベルリンを往復しています。それぞれの飛行機の位置追跡はC++で実装されています。オスロ-ベルリンの飛行機はQMLで操縦され、ロンドン-ベルリンの飛行機はC++で操縦されます。

例の実行

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。

概要

このサンプルは、位置コントローラの実装の一部としてQ_GADGET の機能を利用しています。これにより、QObject 以外の C++ 値型を QML に直接統合することができます。

PlaneController クラスの主な目的は、ある時刻における平面の現在の座標を追跡することです。このクラスは、positionプロパティを通して、その位置を公開します。

class PlaneController: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QGeoCoordinate position READ position WRITE setPosition NOTIFY positionChanged)
    // ...
};

この例のmain() 関数は、PlaneController クラスのインスタンスを QML コンテキストにバインドする役割を担っています:

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    PlaneController oslo2berlin;
    PlaneController berlin2london;

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("oslo2Berlin", &oslo2berlin);
    engine.rootContext()->setContextProperty("berlin2London", &berlin2london);
    engine.load(QUrl(QStringLiteral("qrc:/planespotter.qml")));

    return app.exec();
}

QObject の派生クラスと同様に、QGeoCoordinate はQMLラッパーを追加することなく統合することができます。

飛行機の操縦

上述したように、PlaneController クラスの第一の目的は、2つの飛行機(オスロ-ベルリン、ロンドン-ベルリン)の現在位置を追跡し、QML レイヤーのプロパティとして広告することです。副次的な目的は、与えられた飛行経路に沿って飛行機を設定し、進行させることです。いわば、パイロットのようなものです。これは、ある地理座標から別の地理座標への遷移をアニメーション化できるCoordinateAnimation によく似ています。この例では、PlaneController の position プロパティが、PlaneController 自体の操縦能力を使用する C++ コードと、CoordinateAnimation をパイロットとして使用する QML コードによって、どのように変更されるかを示します。オスロ - ベルリン間の飛行機は QML コードでアニメーションされ、ロンドン - ベルリン間の飛行機は C++ コードでアニメーションされます。

どちらのパイロットを使っても、パイロットの操作結果はC++とQMLで見ることができ、C++とQMLの境界を介した位置データの直接的なやりとりを示しています。

Plane の視覚的表現は、マップに任意のQtQuick アイテムを埋め込むことができるMapQuickItem タイプを使用して行われます:

// Plane.qml
MapQuickItem {
    id: plane
    property string pilotName;
    property int bearing: 0;

    anchorPoint.x: image.width/2
    anchorPoint.y: image.height/2

    sourceItem: Item {
        //...
    }
}
C++パイロット

C++プレーンはC++によって操縦されます。コントローラクラスのfromto プロパティは、パイロットが飛行機の方位を計算するために使用する原点と目的地を設定します:

Q_PROPERTY(QGeoCoordinate from READ from WRITE setFrom NOTIFY fromChanged)
Q_PROPERTY(QGeoCoordinate to READ to WRITE setTo NOTIFY toChanged)

パイロットは、QBasicTimerQTimerEvents を使って常に位置を更新する。各タイマーの反復中にPlaneController::updatePosition() が呼び出され、新しい位置が計算される。

void updatePosition()
{
    // simple progress animation
    qreal progress;
    QTime current = QTime::currentTime();
    if (current >= finishTime) {
        progress = 1.0;
        timer.stop();
    } else {
        progress = ((qreal)startTime.msecsTo(current) / ANIMATION_DURATION);
    }

    setPosition(coordinateInterpolation(
                      fromCoordinate, toCoordinate, easingCurve.valueForProgress(progress)));

    if (!timer.isActive())
        emit arrived();
}

新しい位置が計算されると、setPosition() が呼び出され、その後のプロパティの変更通知により、新しい位置がQMLレイヤーにプッシュされます。

C++ 平面は、平面をクリックすることで開始されます:

Plane {
    id: cppPlane
    pilotName: "C++"
    coordinate: berlin2London.position

    TapHandler {
        onTapped: {
            if (cppPlaneAnimation.running || berlin2London.isFlying()) {
                console.log("Plane still in the air.");
                return;
            }

            berlin2London.swapDestinations();
            cppPlaneAnimation.rotationDirection = berlin2London.position.azimuthTo(berlin2London.to)
            cppPlaneAnimation.start();
            cppPlane.departed();
        }
    }
}

azimuthTo() はある座標から別の座標への方位を度単位で計算します。上記のコードでは、QMLアニメーションを利用して、回転と位置の変更を1つのアニメーションフローにまとめています:

SequentialAnimation {
    id: cppPlaneAnimation
    property real rotationDirection : 0;
    NumberAnimation {
        target: cppPlane; property: "bearing"; duration: 1000
        easing.type: Easing.InOutQuad
        to: cppPlaneAnimation.rotationDirection
    }
    ScriptAction { script: berlin2London.startFlight() }
}

まず、NumberAnimation が平面を正しい方向に回転させ、それが終わると、startFlight() 関数が平面の位置変更を開始します。

public slots:
    void startFlight()
    {
        if (timer.isActive())
            return;

        startTime = QTime::currentTime();
        finishTime = startTime.addMSecs(ANIMATION_DURATION);

        timer.start(15, this);
        emit departed();
    }
QMLパイロット

CoordinateAnimation 、オスロからベルリンへの飛行を制御します。これは上記のScriptAction を置き換えるものです。

CoordinateAnimation {
    id: coordinateAnimation; duration: 5000
    target: oslo2Berlin; property: "position"
    easing.type: Easing.InOutQuad
}

QML飛行機のTapHandler はコース設定のロジックを実装し、必要なときにアニメーションを開始します。

TapHandler {
    onTapped: {
        if (qmlPlaneAnimation.running) {
            console.log("Plane still in the air.");
            return;
        }

        if (oslo2Berlin.position === berlin) {
            coordinateAnimation.from = berlin;
            coordinateAnimation.to = oslo;
        } else if (oslo2Berlin.position === oslo) {
            coordinateAnimation.from = oslo;
            coordinateAnimation.to = berlin;
        }

        qmlPlaneAnimation.rotationDirection = oslo2Berlin.position.azimuthTo(coordinateAnimation.to)
        qmlPlaneAnimation.start()
    }
}

サンプルプロジェクト @ code.qt.io

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。