charles
charles

Reputation: 322

Animating MapQuickItem in QML on position updates

I have an QAbstractListModel object that maintains a list of items to show on a map. The position of these items changes every few seconds and it is easy to calculate a pretty accurate position 60 seconds in the future. What I am trying to do is to set the item's position when it gets a new position (that part works well) and then to immediately move the item toward the calculated future position.

The code without animation looks like this and it works fine:

    Component {
       id: drawTarget

       MapQuickItem {
           id: marker

           coordinate: data.coords

           sourceItem: Item {
               id: item
               ...

The data object has a property which returns the estimated position of the item 60 seconds in the future, so I tried this:

    Component {
       id: drawTarget

       MapQuickItem {
           id: marker

           coordinate: data.coords

           CoordinateAnimation {
               id:anim
               property: "coordinate"
           }

           onCoordinateChanged: {
               anim.stop()
               anim.from = data.coords
               anim.to = data.coordsIn60sec
               anim.duration = 60000
               anim.start()
           }           

           sourceItem: Item {
               id: item
               ...

But although the object's position is updated properly at each position update, the animation toward the future estimated position doesn't work at all.

How would one go about doing something like this?

Upvotes: 2

Views: 1373

Answers (1)

eyllanesc
eyllanesc

Reputation: 244212

In its code, it makes a binding coordinate: data.coords that states that "coordinate" takes the value of "coords" but at the same time says that "coordinate" depends on the animation, isn't it contradictory? Well, it is contradictory.

The idea is not to do the binding coordinate: data.coords but to update the property only through the animation.

The following code is a workable example:

main.qml

import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 5.6
import QtPositioning 5.6

Window {
    visible: true
    width: 640
    height: 480
    Plugin {
        id: mapPlugin
        name: "osm"
    }
    Map {
        anchors.fill: parent
        plugin: mapPlugin
        center: QtPositioning.coordinate(59.91, 10.75) // Oslo
        zoomLevel: 10
        MapItemView{
            model: datamodel
            delegate: MapQuickItem{
                id: item
                // begin configuration
                property var position: model.position
                property var nextposition: model.nextposition
                onPositionChanged: restart();
                onNextpositionChanged: restart();
                function restart(){
                    anim.stop()
                    anim.from = position
                    anim.to = nextposition
                    anim.start()
                }
                CoordinateAnimation {
                    id: anim
                    target: item
                    duration: 60 * 1000
                    property: "coordinate"
                }
                // end of configuration
                anchorPoint.x: rect.width/2
                anchorPoint.y: rect.height/2
                sourceItem: Rectangle{
                    id: rect
                    color: "green"
                    width: 10
                    height: 10
                }
            }
        }
    }
}

datamodel.h

#ifndef DATAMODEL_H
#define DATAMODEL_H

#include <QAbstractListModel>
#include <QGeoCoordinate>
#include <QTimer>
#include <random>

#include <QDebug>

struct Data{
    QGeoCoordinate position;
    QGeoCoordinate nextposition;
};

static QGeoCoordinate osloposition(59.91, 10.75); // Oslo;

class DataModel : public QAbstractListModel
{
    Q_OBJECT
    QList<Data> m_datas;
public:
    enum PositionRoles {
        PositionRole = Qt::UserRole + 1,
        NextPositionRole
    };
    explicit DataModel(QObject *parent = nullptr)
        : QAbstractListModel(parent)
    {
        init();
    }
    int rowCount(const QModelIndex &parent = QModelIndex()) const override{
        return parent.isValid() ? 0: m_datas.count();
    }
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override{
        if (!index.isValid() || index.row() < 0 || index.row() >= m_datas.count())
            return QVariant();
        const Data &data = m_datas[index.row()];
        if (role == PositionRole)
            return QVariant::fromValue(data.position);
        else if (role == NextPositionRole)
            return QVariant::fromValue(data.nextposition);
        return QVariant();
    }
    QHash<int, QByteArray> roleNames() const override{
        QHash<int, QByteArray> roles;
        roles[PositionRole] = "position";
        roles[NextPositionRole] = "nextposition";
        return roles;
    }
private:
    void init(){
        for(int i=0; i< 10; ++i){
            Data data;
            data.position = osloposition;;
            data.nextposition = data.position;
            m_datas << data;
        }
        QTimer *timer = new QTimer(this);
        QObject::connect(timer, &QTimer::timeout, this, &DataModel::updateData);
        timer->start(60 * 1000);
        updateData();
    }
    void updateData(){
        qDebug() << __PRETTY_FUNCTION__;
        static std::default_random_engine e;
        static std::uniform_real_distribution<> dis(-.1, .1);
        for(int i=0; i < m_datas.count(); ++i){
            Data & data = m_datas[i];
            QModelIndex ix = index(i);
            data.position = data.nextposition;
            data.nextposition = QGeoCoordinate(osloposition.latitude() + dis(e),
                                           osloposition.longitude() + dis(e));
            Q_EMIT dataChanged(ix, ix, {PositionRole, NextPositionRole});
        }
    }
};

#endif // DATAMODEL_H

In the following link is the complete example

Upvotes: 2

Related Questions