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.

Figure 4: Your app will show the position of your phone on a map. As it moves, the red circle will follow it around.

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.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95

News