Provide position through QGeoPositionInfoSource

1141
4
12-23-2019 04:56 AM
Arjenvan_Heteren
New Contributor

Dear all, I am making an application which must show the location of a ship. The actual location comes from outside the application which I am making. To show the location, I want to make use of the LocationDisplay class. So I started a default ArcGIS application in Qt Creator (I tried both a QML and C++ app), and made a class, which is derived from QGeoPositionInfoSource, according to this example(https://github.com/Esri/dynamic-situational-awareness-qt/blob/master/Shared/GPXLocationSimulator.h), linked by LDanzinger-esristaff, also on this forum. However, I just can't seem to get it to work. It seems I just don't understand it, and to be hones I'm getting a bit frustrated

My code is as follows:

The header:

#ifndef SHIPPOSITIONSOURCE_H
#define SHIPPOSITIONSOURCE_H

#include <QGeoPositionInfoSource>

class ShipPositionSource : public QGeoPositionInfoSource
{
Q_OBJECT

public:
explicit ShipPositionSource(QObject* parent = nullptr);
~ShipPositionSource();

QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const override;
PositioningMethods supportedPositioningMethods() const override;
int minimumUpdateInterval() const override;
QGeoPositionInfoSource::Error error() const override;

public slots:
void startUpdates() override;
void stopUpdates() override;
void requestUpdate(int timeout = 0) override;

private:

QGeoPositionInfo currentPosition;
QGeoPositionInfoSource::Error lastError = QGeoPositionInfoSource::NoError;

bool started;
};

#endif // SHIPPOSITIONSOURCE_H


The source:

#include "shippositionsource.h"

ShipPositionSource::ShipPositionSource(QObject* parent) :
QGeoPositionInfoSource(parent)
{
   setUpdateInterval(500);
   currentPosition.setCoordinate(QGeoCoordinate(52.920905, 4.862173));
   emit positionUpdated(currentPosition);
}

ShipPositionSource::~ShipPositionSource()
{}

void ShipPositionSource::startUpdates()
{
   if(started)
   return;

   started = true;
   //TODO: start communication with bridge
}

void ShipPositionSource::stopUpdates()
{
   if(!started)
   return;

   started = false;

   //TODO: stop communication with bridge
}

void ShipPositionSource::requestUpdate(int timeout)
{
   Q_UNIMPLEMENTED();
}

QGeoPositionInfo ShipPositionSource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
{
   Q_UNUSED(fromSatellitePositioningMethodsOnly);
   return currentPosition;
}

QGeoPositionInfoSource::PositioningMethods ShipPositionSource::supportedPositioningMethods() const
{
   return QGeoPositionInfoSource::PositioningMethod::NoPositioningMethods;
}

int ShipPositionSource::minimumUpdateInterval() const
{
   return updateInterval();
}

QGeoPositionInfoSource::Error ShipPositionSource::error() const
{
   return lastError;
}


I made a Quick C++ app, called TestProject, and in TestProject.cpp I only edited the TestProject::setMapView method with the following lines:

m_mapView = mapView;
m_mapView->locationDisplay()->setPositionSource(&positionSource);
m_mapView->locationDisplay()->setAutoPanMode(LocationDisplayAutoPanMode::Recenter);
m_mapView->locationDisplay()->start();
m_mapView->setMap(m_map);

The result is that I don't see any location updated, or the map centering on the position I gave in the constructor. I must be doing something wrong or I don't understand something, but I don't see what. I would be very thankful for hints and tips!

0 Kudos
4 Replies
GuillaumeBelz
Esri Contributor

Hi Arjen van Heteren

The "updated" signals should be emitted when the map is ready to handle them. If you're emitting a signal in the constructor, it's never possible to connect to the signal. The best is probably to use a timer, like in the Lucas' code (with "setSingleShot(true)" if you want to emit only once).

Note: don't forget to initialize "started" before use it.

0 Kudos
Arjenvan_Heteren
New Contributor

Oh wow, I should've thought of that myself. It works now, thank you very much Guillaume Belz!

Now I want to do the same in a QML app. So I made a default ArcGIS Qt Quick QML app, copied my now working shippositionsource in this project, and registered this class with the following line: 

qmlRegisterType<ShipPositionSource>("Ship.Source", 1, 0, "ShipPositionSource");

In the main.qml, I added of course the import:

import Ship.Source 1.0

However, I'm now getting stuck at how to get this to work. My complete main.qml file is as follows:


// Copyright 2019 ESRI
//
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
//
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
//
// See the Sample code usage restrictions document for further information.
//

import QtPositioning 5.12
import QtQuick 2.6
import QtQuick.Controls 2.13
import QtSensors 5.12

import Esri.ArcGISRuntime 100.7
import Ship.Source 1.0

ApplicationWindow
{
id: appWindow
width: 800
height: 600
title: "Gui"

property double mapMenuRatio: 0.7
property double maxMapMenuRation: 0.9
property double minMapMenuRation: 0.5


SplitView
{
id: splitViewBase
anchors.fill: parent
orientation: Qt.Horizontal

Rectangle {
id: centerItem
SplitView.minimumWidth: 50
SplitView.fillWidth: true
color: "black"

MapView
{
id: mapView
anchors.fill: parent
// set focus to enable keyboard navigation
focus: true

// add a map to the mapview
Map
{
id: map
// add the BasemapOceans basemap to the map
BasemapOceans {}
}

locationDisplay
{
positionSource :ShipPositionSource{}
autoPanMode: Enums.LocationDisplayAutoPanModeRecenter
}
}
}

Rectangle {
id: menu

implicitWidth: appWindow.width * (1 - mapMenuRatio)
SplitView.maximumWidth: appWindow.width * (1 - minMapMenuRation)
color: "brown"

//MenuFrame
//{
// anchors.fill: parent
//}
}
}
}

Due to some debug logging, I can see the ShipPositionSource has been created, but I can't actually see the position yet. I think I have to enable the locationDisplay, but I can't seem to find how to do this.

Any help would be very much appreciated again!

0 Kudos
GuillaumeBelz
Esri Contributor

You're just need to call start, for example in Component.onCompleted:

Component.onCompleted: mapView.locationDisplay.start();
0 Kudos
Arjenvan_Heteren
New Contributor

Thank you for your answer!

I added a Component.onCompleted in the MapView (id: mapView), which I validated was called by also putting in a log line.

However, when I ran the application, I saw in the output the following line:

QMetaObject::invokeMethod: No such method ShipPositionSource::start()

So I added a public slot, called start(), in my ShipPositionSource class. Unfortunately, nothing happened yet. So I thought, maybe I have to start the position source itself somehow. So I added a oncompleted event to the positionsource, where I call startUpdates(). However, this also didn't work.

For clarity, my MapView now looks as follows:

MapView
{
id: mapView
anchors.fill: parent
// set focus to enable keyboard navigation
focus: true

// add a map to the mapview
Map
{
id: map
// add the BasemapOceans basemap to the map
BasemapOceans {}
}

locationDisplay
{
positionSource: ShipPositionSource
{
id: src

onPositionUpdated:
{
console.log("onPositionUpdated called");
}

Component.onCompleted:
{
src.startUpdates();
}
}

autoPanMode: Enums.LocationDisplayAutoPanModeRecenter
wanderExtentFactor: 0.1
}

Component.onCompleted:
{
locationDisplay.start();
console.log("on completed called");
}
}

My ShipPositionSource class hasn't been changed from the one in my starting post, with the exception of a timer, which gives a new position every 2 seconds, via the following line:

emit positionUpdated(currentPosition);

I'm looking forward to any advice.

0 Kudos