Location, Location, Location
Mapping Out
The objective is to show a map of the GPS device's location, and, as it moves, have the map move with it (see Figure 4). A circle indicates where the device is located on the map.
Your first reaction would probably be to base your app on some open source map app that already exists. Marble [4] seems like a good candidate, and if you run it like so
marble --latlon <lat>,<lon> --distance 2 --map "earth/openstreetmap/openstreetmap.dgml"
where lat
and lon
are the latitude and longitude, Marble will open at the desired location, which is already promising.
Then, if you think that
gpspipe -w | sed -n s/.*lat:(.*),/1/p | cut -d, -f1
will give you the latitude of your phone, and
gpspipe -w | sed -n s/.*lon:(.*),/1/p | cut -d, -f1
will give you the longitude, things definitely seem to be shaping up.
The preceding instructions will give you an ongoing stream of latitudes and longitudes. It would be possible to pipe the output to a file and then run through the rows and use them with Marble somehow. But this is an inelegant and convoluted way of doing things. It is much smarter to break out QML [5].
QML App
QML is a relatively simple declarative language that uses the Qt libraries to build graphical applications. How QML works is a bit like how you'd go about programming an interface using a visual IDE, but with text: You "design" the layout describing it in text form, and then you do the same with all the items contained in the layout. To execute actions (say, when a button is pressed), you associate events with JavaScript modules to each object.
Getting back to the original idea, the Marble documentation says that it is easy to develop apps based on the Marble code in QML. It refers the would-be programmer to a tutorial and examples available at [6]. Unfortunately, the code examples and snippets are outdated, and it is quite hard to figure out how to do things. According to one developer, the problem is that Marble's code is in continuous flux, changing nearly daily.
Instead, he suggests using QML's own Location library. To get started, install all the bits and pieces you'll need:
apt install qmlscene qml-module-qtpositioning qml-module-qtlocation
The modules will install positioning and location libraries for your app and drag in all the Qt QML common files. The qmlscene
utility is what you use to run QML apps.
The code for your app might look like Listing 2.
Listing 2
map.qml
01 import QtQuick 2.1 02 import QtQuick.Window 2.0 03 import QtPositioning 5.5 04 import QtLocation 5.6 05 06 Window { 07 id:page 08 width: 800 09 height: 800 10 visible: true 11 12 Map { 13 id:myMap 14 anchors.fill: parent 15 plugin: mapPlugin 16 zoomLevel: 17 17 18 property MapCircle circle 19 20 function update(pos) { 21 removeMapItem(circle); 22 23 circle = Qt.createQmlObject('import QtLocation 5.3; MapCircle {}', page); 24 circle.radius = 30; 25 circle.color = "transparent"; 26 circle.border.color = "red" 27 circle.border.width = 3; 28 myMap.addMapItem(circle); 29 30 circle.center = pos.coordinate; 31 myMap.center = pos.coordinate; 32 33 //console.log("Coordinates: ", pos.coordinate.latitude, pos.coordinate.longitude); 34 } 35 } 36 37 Plugin { 38 id: mapPlugin 39 name: "osm" 40 } 41 42 PositionSource { 43 id: gpsPos 44 updateInterval: 500 45 active: true 46 nmeaSource: "socket://localhost:29999" 47 48 onPositionChanged: { 49 myMap.update(position); 50 } 51 } 52 }
After loading in all the modules you need (lines 1-4), you create an all-encompassing Window
object (line 6). You give it a name (page
), set its size, and make it visible.
Next you create a Map
widget. Again, you give it a name (myMap
), you set its size to automatically fit in Window
(the parent
object), and you set the plugin it will use. In this case, the plugin is declared on lines 37 to 40, where you can see you will be using the OpenStreetMap [7] data for your maps. Finally, you set the zoom level of the map, in this case, 17 meters up in the air.
Map
objects allow you to draw shapes on the map. If you want a circle, you create a circle
object on line 18. You then manipulate it using a JavaScript function (lines 20-34) and moving it (line 30) when the GPS position changes. You also move the view of the map so that the circle is always in the center (line 31) .
Line 33 is for debugging and testing only. Uncomment it to see coordinates scroll by on the terminal window as you move around.
As mentioned previously, QML is declarative and event-based and you can see how that works in the PositionsSource
object (lines 42-49). This is an object provided by the QtPositioning
module, and it is in charge of retrieving and processing the GPS information.
As usual, you give your object a name (gpsPos
) to refer to it later, you set how often it will poll the GPS source (once every 500 milliseconds), and you set from where it will get the NMEA information.
A slight detour here, because it turns out that, unless you have GPS on the laptop or a GPS-only device connected via USB (that is, not a phone), PositionSource
does a poor job of determining where it should get its data.
However, you can force it to look for data by setting the nmeaSource
attribute. You would usually nmeaSource
to point to a file with static NMEA data to "simulate" a GPS positioning (nmeaSource
is primarily used when testing), but you can also point to a server. This got me thinking: First I tried to pipe the output from gpspipe
to a file and read from that:
gpspipe -r > gps.dat
Line 46 then looked like this:
nmeaSource: "gps.dat"
But then PositionSource
was never updated – it just took the last value from the file and never went back to check if there were any more.
A bit more research revealed that PositionSource
can also be read from a socket URL. And know what makes sockets super easy? Netcat [8] does. Netcat (nc
) is preinstalled in nearly every Linux installation on the planet, so it was simply a matter of doing this:
gpspipe -r | nc -l 29999
Then, create a stream of GPS data on localhost
using port 29999
, and get PositionSource
to read from it, as shown on line 46.
Finally, you set up an event that fires when the position you are polling changes (line 48). When it does fire, you visit the update(pos)
function in the Map
object (lines 20-34) and redraw the circle and the map with the new position data, pos.coordinate
, that you pass in the function call.
To summarize, here's what you do to make everything work together:
1. Run gpsd udp://*.29998
.
2. Connect your phone's GPS client to your laptop using your laptop's IP and port 29998.
3. Run gpsmon
to check you are receiving GPS data correctly.
4. Run gpspipe -r | nc -l 29999
to set up a data stream through port 29999 on your computer.
5. Run your application with qmlscene map.qml
.
Conclusion
There you have it: A full GPS app on a laptop (or Raspberry Pi) in 50 lines of code [9]. This is definitely a fun project, and it combines hardware you probably already have hanging around at home in an interesting way. Expanding to build an actually useful application should be a piece of cake.
Famous last words.
Infos
- gpsd: http://www.catb.org/gpsd/
- gpsd client on Google Play: https://play.google.com/store/apps/details?id=io.github.tiagoshibata.gpsdclient
- A (free) gpsd client for Android: https://github.com/tiagoshibata/Android-GPSd-Client
- Marble: https://marble.kde.org/
- QML: https://doc.qt.io/qt-5.10/qtqml-index.html
- Marble for developers: https://marble.kde.org/dev-intro.php
- OpenStreetMap project: https://www.openstreetmap.org/
- Netcat: http://nc110.sourceforge.net/
- Video of the app in action: https://youtu.be/jcHOY07Rvpk
« Previous 1 2
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.