0
votes

I want to display a huge number of points on a map with Qt/QML. These points are detailled in a .txt file.

I'm looking for the process to use but, unfortunately, I only find a query method via a plugin to display places from a server (osm, google...). I can't use it, these points being very specific.

What is the best way to achieve this task ?

Also, is it necessary to use "plugin itemsoverlay" in addition to the "osm plugin" to show these points on a specific layer ?

Thanks for help.

Ok, back to fight with code now.

Here is the main .cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QObject>
#include <QGeoCoordinate>
#include <QDebug>

class NavaidsPoint: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QGeoCoordinate position READ position WRITE setPosition NOTIFY positionChanged)

public:
    NavaidsPoint(QString code, double latitude, double longitude, QString country = "")
    {
        m_code = code;
        m_latitude = latitude;
        m_longitude = longitude;
        m_country = country;
        m_position.setLatitude(latitude);
        m_position.setLongitude(longitude);
    }

    void setPosition(const QGeoCoordinate &c) { //Affectation des nouvelles coordonnées de position
        if (m_position == c)
            return;

        m_position = c;
        emit positionChanged(); //Emission du signal de changement de position
    }

    QGeoCoordinate position() const
    {
        return m_position; //Lecture des coordonnées de position
    }

    Q_INVOKABLE QString oaciCode() const {
        return m_code;
    }

    Q_INVOKABLE QString countryCode() const {
        return m_country;
    }

signals:
    void positionChanged();

private:
    QGeoCoordinate m_position;
    double m_latitude;
    double m_longitude;
    double m_altitude;
    QString m_code;
    QString m_country;
};




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

    NavaidsPoint oslo("Oslo", 59.9154, 10.7425, "NG");

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("oslo", &oslo);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

#include "main.moc"

And the main.qml

import QtQuick 2.6
import QtQuick.Window 2.2
import QtPositioning 5.5
import QtLocation 5.6

Window {
    width: 700
    height: 500
    visible: true
    title: qsTr("Test implantation coordonnées")

    property variant topLeftEurope: QtPositioning.coordinate(60.5, 0.0)
    property variant bottomRightEurope: QtPositioning.coordinate(51.0, 14.0)
    property variant viewOfEurope:
            QtPositioning.rectangle(topLeftEurope, bottomRightEurope)

    Map {
        id: mapOfEurope
        anchors.centerIn: parent;
        anchors.fill: parent
        plugin: Plugin {
            name: "osm"
        }

        MapCircle {
            center: oslo.position
            radius: 5000.0
            color: 'green'
            border.width: 3
            MouseArea {
                anchors.fill: parent
                onDoubleClicked: {
                    console.log("Doubleclick on " + oslo.oaciCode())
                }
                onClicked: {
                    console.log("Point : " + oslo.oaciCode() + " " + oslo.position + " " + oslo.countryCode())
                }
            }
        }
    visibleRegion: viewOfEurope
    }
}

everything works fine with this unique point Oslo. Now I need to place thousands of points. This structure cannot work like that, because I need to implement one NavaidsPoint for each of them and setContextProperty for each of them again. Likewise, in the main.QML the circle is tied with the object :

center : oslo.position

and in addition, I nead the oaciCode and the countryCode. So this part of the code must be generic, not specific to a single object.

So, what could be the best way to solve this problem ?

I hope to be in the SO scope with these precisions.

Thanks again for help.

1
show your .txt please - eyllanesc
ROBSO,21.75000,-107.12556,MM ROBSU,49.83348,-64.04158,CY ROBTE,45.40642,-91.83210,K5 ROBUC,41.67892,-71.58512,K6 ROBUD,40.64376,-118.70789,K2 ROBUE,62.45198,-160.23723,PA ROBUM,69.43252,15.91136,EN ROBUN,74.83083,74.18083,UL ROBUR,61.88620,-6.57648,EK ROBUS,55.10944,11.71972,EK ROBUT,46.00639,42.70333,UR ROBVE,36.30598,-99.19448,K4 ROBVI,52.57165,3.77645,EH ROBVO,51.68444,-8.19925,EI ROBVY,38.78295,-106.10674,K2 ROBYE,35.72012,-89.81856,K7 ROBYN,19.76366,-156.11584,PH ... here is a snippet and some thousands more rows. - kontiki
I recommend trying to solve your problem, if you have any difficulty then publish a question detailing the error you have and showing the code you have tried. Then we will try to help you. SO is not a coding service. - eyllanesc
Thanks eyllanesc. Sorry for the inconvenience - kontiki

1 Answers

0
votes

If you want to load many elements a good option is to use MapItemView, this requires a model that provides the data, and indicate the appropriate delegate.

The model is created based on a QAbstractListModel where we will use the roles to expose the properties to QML, for it creates a class that does not necessarily have to inherit from QObject, in this case it will be the following.

#ifndef NAVAIDSPOINT_H
#define NAVAIDSPOINT_H

#include <QGeoCoordinate>
#include <QString>

class NavaidsPoint
{
public:
    NavaidsPoint(QString code, double latitude, double longitude, QString country = ""){
        m_code = code;
        m_country = country;
        m_position.setLatitude(latitude);
        m_position.setLongitude(longitude);
        m_position.setAltitude(0.0);
    }

    void setPosition(const QGeoCoordinate &c) { //Affectation des nouvelles coordonnées de position
        m_position = c;
    }

    QGeoCoordinate position() const{
        return m_position; //Lecture des coordonnées de position
    }

    QString oaciCode() const {
        return m_code;
    }

    QString countryCode() const {
        return m_country;
    }

private:
    QGeoCoordinate m_position;
    QString m_code;
    QString m_country;
};


#endif // NAVAIDSPOINT_H

QAbstractListModel requires that you implement the methods rowCount, data and roleNames, also I have implemented the add method where you must notify the model of the changes, an example is as follow

#ifndef NAVAIDSMODEL_H
#define NAVAIDSMODEL_H

#include "navaidspoint.h"

#include <QAbstractListModel>
#include <QFile>
#include <QTextStream>

#include <QDebug>

class NavaidsModel : public QAbstractListModel
{
    Q_OBJECT
public:
    NavaidsModel(QObject *parent = Q_NULLPTR):QAbstractListModel(parent){
    }
    enum NavaidsRoles {
        PositionRole = Qt::UserRole + 1,
        OACICodeRole,
        CountryCodeRole
    };

    void readFromCSV(const QString &filename){
        QFile file(filename);
        if(!file.open(QFile::ReadOnly | QFile::Text))
            return;
        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine();
            QStringList elements = line.split(",");
            if(elements.count()==4){
                QString code = elements[0];
                double latitude = elements[1].toDouble();
                double longitude = elements[2].toDouble();
                QString country = elements[3];
                NavaidsPoint p(code, latitude, longitude, country);
                addNavaidsPoint(p);
            }
        }
    }

    void addNavaidsPoint(const NavaidsPoint &point){
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        mPoints << point;
        endInsertRows();
    }

    int rowCount(const QModelIndex & parent = QModelIndex()) const{
        Q_UNUSED(parent)
        return mPoints.count();
    }

    QVariant data(const QModelIndex & index, int role=Qt::DisplayRole) const {
        if (index.row() < 0 || index.row() >= mPoints.count())
            return QVariant();

        const NavaidsPoint &point = mPoints[index.row()];
        if (role == PositionRole)
            return QVariant::fromValue(point.position());
        else if (role == OACICodeRole)
            return point.oaciCode();
        else if (role == CountryCodeRole)
            return point.countryCode();
        return QVariant();
    }

protected:
    QHash<int, QByteArray> roleNames() const {
        QHash<int, QByteArray> roles;
        roles[PositionRole] = "position";
        roles[OACICodeRole] = "oaci";
        roles[CountryCodeRole] = "country";
        return roles;
    }
private:
    QList<NavaidsPoint> mPoints;
};

#endif // NAVAIDSMODEL_H

After the model is created, the models are added and we expose it to QML:

#include "navaidsmodel.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    NavaidsModel model;
    model.readFromCSV("/home/eyllanesc/data.csv"); //from file

    model.addNavaidsPoint(NavaidsPoint("Oslo", 59.9154, 10.7425, "NG")); // add new point

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("navaidsModel", &model);
    engine.load(QUrl(QLatin1String("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

and at the end, MapItemView is used with the appropriate model and delegate:

import QtQuick 2.6
import QtQuick.Window 2.2
import QtPositioning 5.5
import QtLocation 5.6

Window {
    width: 700
    height: 500
    visible: true
    title: qsTr("Test implantation coordonnées")

    property variant topLeftEurope: QtPositioning.coordinate(60.5, 0.0)
    property variant bottomRightEurope: QtPositioning.coordinate(51.0, 14.0)
    property variant viewOfEurope:
        QtPositioning.rectangle(topLeftEurope, bottomRightEurope)

    Map {
        id: mapOfEurope
        anchors.centerIn: parent;
        anchors.fill: parent
        plugin: Plugin {
            name: "osm"
        }
        MapItemView {
            model: navaidsModel
            delegate: MapCircle{
                center: position
                radius: 10000
                color: 'green'
                border.width: 3
                MouseArea {
                    anchors.fill: parent
                    onDoubleClicked: {
                        console.log("Doubleclick on " + oaci)
                    }
                    onClicked: {
                        console.log("Point : " + oaci + " " + position + " " + country)
                    }
                }
            }
        }
        visibleRegion: viewOfEurope
    }
}

In the following link is the complete example.