Display device location with NMEA data sources

View inC++QMLView on GitHubSample viewer app

Parse NMEA sentences and use the results to show device location on the map.

screenshot

Use case

NMEA sentences can be retrieved from GPS receivers and parsed into a series of coordinates with additional information. Devices without a built-in GPS receiver can retrieve NMEA sentences by using a separate GPS dongle, commonly connected bluetooth or through a serial port.

The NMEA location data source allows for detailed interrogation of the information coming from the GPS receiver. For example, allowing you to report the number of satellites in view.

How to use the sample

Tap "Start" to parse the NMEA sentences into a simulated location data source, and initiate the location display. Tap "Reset" to reset the location display.

How it works

  1. Load an NMEA string and parse the sentences into a series of locations.
  2. Create an NmeaLocationDataSource and initialize it with the mock locations.
  3. Set it to the location display's data source.
  4. Start the location display to begin receiving location and satellite updates.

Relevant API

  • Location
  • LocationDisplay
  • NmeaLocationDataSource

About the data

This sample reads lines from a local file to simulate the feed of data into the NmeaLocationDataSource. This simulated data source provides NMEA data periodically, and allows the sample to be used on devices without a GPS dongle that produces NMEA data.

The route taken in this sample features a two-minute driving trip around Redlands, CA.

Tags

dongle, GPS, history, navigation, NMEA, real-time, trace

Sample Code

DisplayDeviceLocationWithNmeaDataSources.cppDisplayDeviceLocationWithNmeaDataSources.cppDisplayDeviceLocationWithNmeaDataSources.hDisplayDeviceLocationWithNmeaDataSources.qml
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// [WriteFile Name=DisplayDeviceLocationWithNmeaDataSources, Category=Maps]
// [Legal]
// Copyright 2021 Esri.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]

#ifdef PCH_BUILD
#include "pch.hpp"
#endif // PCH_BUILD

#include "DisplayDeviceLocationWithNmeaDataSources.h"

#include "Map.h"
#include "MapQuickView.h"
#include "NmeaLocationDataSource.h"

#include <QFile>
#include <QTimer>

using namespace Esri::ArcGISRuntime;

DisplayDeviceLocationWithNmeaDataSources::DisplayDeviceLocationWithNmeaDataSources(QObject* parent /* = nullptr */):
  QObject(parent),
  m_map(new Map(BasemapStyle::ArcGISNavigation, this)),
  m_timer(new QTimer(this))
{
}

DisplayDeviceLocationWithNmeaDataSources::~DisplayDeviceLocationWithNmeaDataSources() = default;

void DisplayDeviceLocationWithNmeaDataSources::init()
{
  // Register the map view for QML
  qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView");
  qmlRegisterType<DisplayDeviceLocationWithNmeaDataSources>("Esri.Samples", 1, 0, "DisplayDeviceLocationWithNmeaDataSourcesSample");
}

MapQuickView* DisplayDeviceLocationWithNmeaDataSources::mapView() const
{
  return m_mapView;
}

// Set the view (created in QML)
void DisplayDeviceLocationWithNmeaDataSources::setMapView(MapQuickView* mapView)
{
  if (!mapView || mapView == m_mapView)
    return;

  m_mapView = mapView;
  m_mapView->setMap(m_map);

  m_mapView->setViewpointCenter(Point(-117.191, 34.0306, SpatialReference::wgs84()), 100'000);

  // Create a new NMEA location data source
  m_nmeaLocationDataSource = new NmeaLocationDataSource(SpatialReference::wgs84(), this);
  m_mapView->locationDisplay()->setDataSource(m_nmeaLocationDataSource);
  m_mapView->locationDisplay()->setAutoPanMode(LocationDisplayAutoPanMode::Recenter);

  emit mapViewChanged();
}

void DisplayDeviceLocationWithNmeaDataSources::start()
{
  // Enable receiving NMEA location data from external device
  m_nmeaLocationDataSource->start();

  // Display the user's location
  m_mapView->locationDisplay()->start();

  // Load simulated NMEA sentences for sample
  if (m_mockNmeaSentences.isEmpty())
  {
    const QString filePath = ":/Samples/Maps/DisplayDeviceLocationWithNmeaDataSources/redlands.nmea";
    if(!loadMockDataFile(filePath))
    {
      qDebug() << "Unable to load file at path:" << filePath;
      m_nmeaSimulationActive = false;
      emit nmeaSimulationActiveChanged();

      return;
    }
  }

  // Simulate pushing data to the NMEA location data source
  connect(m_timer, &QTimer::timeout, this, [this]()
  {
    const QByteArray line = m_mockNmeaSentences.at(m_mockDataIterator);

    // In a non-simulated scenario, incoming NMEA sentences from a serial port or bluetooth device would be pushed to the location data source in real time
    // NMEA sentences can be pushed individually or in multiple lines separated by line breaks
    // Sentences pass information such as direction, velocity, and location and are grouped together to provide detailed information about a user's position
    m_nmeaLocationDataSource->pushData(line);

    ++m_mockDataIterator;
    if (m_mockDataIterator >= m_mockNmeaSentences.size())
      m_mockDataIterator = 0;
  });

  m_mockDataIterator = 0;
  m_timer->start(1000);
}

void DisplayDeviceLocationWithNmeaDataSources::reset()
{
  // Disable simulated data
  m_timer->stop();
  disconnect(m_timer, &QTimer::timeout, nullptr, nullptr);

  // Stop displaying user's location
  m_mapView->locationDisplay()->stop();

  // Stop receiving location data
  m_nmeaLocationDataSource->stop();
}

bool DisplayDeviceLocationWithNmeaDataSources::loadMockDataFile(const QString& filePath)
{

  // Load simulated NMEA sentences to display for sample
  QFile mockDataFile(filePath);

  if(!mockDataFile.exists())
    return false;

  mockDataFile.open(QIODevice::ReadOnly);

  while (!mockDataFile.atEnd()) {
    QByteArray line = mockDataFile.readLine();

    // In this simulated data stream, blocks of NMEA sentences start with $GPGGA (which provides the device's position)
    if (line.startsWith("$GPGGA"))
      m_mockNmeaSentences.append(line);

    // Additional sentences that provide information such as direction and velocity follow and are separated by line breaks
    else
      m_mockNmeaSentences.last() += line;
  }

  mockDataFile.close();
  return true;
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.