Location and Mapping Services

Written by David Boddie, Qt Development Frameworks

Whether you're out and about or sitting at your desk, some of the location and mapping services available on the Internet can be useful tools for navigation or just an interesting distraction from work.

Download the Code

The recent release of Qt Mobility 1.1 comes with a new set of classes for mapping and a plugin for Nokia's Ovi Maps service, making it easy to explore the world from your comfy office chair.

In this article, we look at the basic mapping capabilities of Qt Mobility 1.1, using it to create maps, annotate them, and explore related concepts such as landmarks. We also briefly consider the possibilities that exist for describing and displaying maps with QML. The complete code is available for download if you want to have a look at it.

graphicsmap
pixmapmap
pixmapmap

 

Finding a Starting Point

QtLocation, one of the modules provided with Qt Mobility, comes with a collection of classes related to various aspects of location. These include hardware-related classes like QGeoPositionInfoSource, which provides information about the user's position using a Global Positioning System (GPS) unit or other location-aware device, and QGeoSatelliteInfoSource, which is used to obtain information about positioning satellites. Although these are interesting to experiment with, we will leave these for the curious developer to try out on their own.

The module also includes a number of general purpose classes for describing location information. The QGeoCoordinate contains the latitude, longitude and altitude of a position on the Earth, and is used throughout the QtLocation API. Other classes, like QGeoAddress and QGeoPlace provide representations of higher-level information about locations. Places may also correspond to landmarks — we will take a look at these later. Classes are also provided ways to represent geographical areas, either in an abstract way, like QGeoBoundingArea, or more precisely, as with QGeoBoundingBox, and QGeoBoundingCircle.

However, the classes we are most interested in are the ones that can access and display map data. The main class for this is QGraphicsGeoMap, an item that is ready for use with QGraphicsScene.

Maps and Services

Before we can get started with maps, scenes and views, we first need to get access to a source of geographical data. The general way to do this is to use QGeoServiceProvider to find out which services are available.

  1.         // Various headers are required - see the full example for details.
  2.         using namespace QtMobility;
  3.  
  4.         int main(int argc, char *argv[])
  5.         {
  6.         QApplication app(argc, argv);
  7.         QStringList services = QGeoServiceProvider::availableServiceProviders();
  8.  
  9.         if (services.isEmpty()) {
  10.                 // Report the situation and exit.
  11.         }

 

If no service providers are available, the QStringList will be empty, so developers need to check for this possibility in their applications and act accordingly.

Assuming that a service provider is available, we can construct an instance of the QGeoServiceProvider class and use it to get access to mapping facilities. By default, there is no built-in support for service providers in the QtLocation module — support for services is provided via plugins. In the following code, we assume that the nokia service provider (corresponding to Nokia's Ovi Maps service) is available since a plugin for it is included in default builds of Qt Mobility:

  1.     QGeoServiceProvider service("nokia");
  2.     if (service.error() != service.NoError) {
  3.         // Report the situation and exit.
  4.     }

 

We should also cover the case where the plugin is not available, perhaps by exiting or by asking the user to select another service provider.

Once a service provider has been obtained, we can ask it for a QGeoMappingManager instance which will let us fetch map images.

  1.     QGeoMappingManager *manager = service.mappingManager();

 

Not all service providers will provide mapping facilities – some may only provide search or routing services – so we should really check that we have obtained a useable instance from the service provider. However, in this simple example, we know that the nokia service provider supports maps, so we assume that we are ready to go once we have a mapping manager.

The same procedure is used when requesting managers for search and routing facilities. Once we have a service provider, we can call its searchManager() and routingManager() functions to access these.

Setting the Scene

graphicsmap example

Graphics Map example.

With a mapping manager in place, we can download map data from the service and present it to the user. Fortunately, we do not have to think too hard about finding a way to display map images because the QtLocation module comes with the QGraphicsGeoMap graphics item that is ready for use with QGraphicsScene. All we need to do is to create a scene and a view in the usual way, and insert one of these items:

  1.     QGraphicsScene scene;
  2.     QGraphicsView view;
  3.     view.setScene(&scene);
  4.  
  5.     QGraphicsGeoMap *geoMap = new QGraphicsGeoMap(manager);
  6.     scene.addItem(geoMap);

 

The mapping manager is passed to the item when it is constructed, enabling it to access the service without requiring our assistance, but first it needs to be given a size, told about the location we want to obtain maps for, and set to use a certain zoom level:

  1.     geoMap->resize(300, 300);
  2.     geoMap->setCenter(QGeoCoordinate(52.75, -2.75));
  3.     geoMap->setMapType(QGraphicsGeoMap::TerrainMap);
  4.     geoMap->setZoomLevel(10);
  5.  
  6.     view.show();
  7.     return app.exec();
  8. }

 

Map Types

The QGraphicsGeoMap class defines the mapType property which can be used to define which kind of map each instance will fetch from the service. The possible map types themselves are described by the MapType an enum, and include regular street map, terrain and satellite images (day and night).

However, not all mapping services provide all kinds of map data, so it is usually a good idea to call the supportedMapTypes() function to obtain a list of map types for the service in use.

The above configuration produces is an square map tile containing a street map. The zoom level was carefully chosen to show the surroundings of a town — the value must be within the range defined by the graphics item's minimumZoomLevel and maximumZoomLevel properties, which have values of 0 and 18 respectively for the nokia service. A zoom level of 0 shows the entire globe.

The full source code for this example is included in the graphicsmaps directory of the examples archive accompanying this article.

Since we are using a graphics item to obtain map images, we might assume that extending an example like this to allow scrolling and panning would involve adding more QGraphicsGeoMap items to the scene. However, this item is really designed to be used as a viewport onto a larger map that the user views by touching and dragging the item. For this reason, it includes the pan() slot that is used to scroll the map by distances measured in widget coordinates. We will briefly look at user interaction later in this article.

Annotating Maps

One of the things we might want to do with the maps obtained from the service is to annotate them with information that may be relevant to the user, perhaps using data from another service. At first glance, it would appear that to do this we would have to start thinking about ways to convert between latitude, longitude and widget coordinates, and QGraphicsGeoMap provides functions to do this. However, for simple text labels and shapes, we can take advantage of some ready-made classes based on QGeoMapObject that work using positions specified with QGeoCoordinate.

Once again, we construct a QGraphicsGeoMap instance and configure it to view the desired portion of the globe:

  1. QGraphicsGeoMap *geoMap = new QGraphicsGeoMap(manager);
  2. // Configure and resize the item.

 

With the map item set up, we can place a text label and a circle on the map to provide extra information about a place. For both of these map objects, we specify their locations using QGeoCoordinate values; for the circle, we also supply a radius specified in meters.

  1. QFont font;
  2. font.setWeight(QFont::Bold);
  3. QGeoMapTextObject *textObj = new QGeoMapTextObject(QGeoCoordinate(46.274, 6.065),
  4.     "Large Hadron Collider\n(approximate location)", font, QPoint(0, 0));
  5. textObj->>setPen(QPen(Qt::NoPen));
  6. textObj->setBrush(QBrush(Qt::white));
  7. geoMap->addMapObject(textObj);
  8.  
  9. QGeoMapCircleObject</a> *circleObj = new QGeoMapCircleObject(>QGeoCoordinate(46.274, 6.065), 4297.183);
  10. circleObj->setPen(QPen(Qt::white, 2, Qt::DotLine));
  11. circleObj->setBrush(QBrush(Qt::NoBrush));
  12. geoMap->addMapObject(circleObj);

 

map-shapes

Map Shapes.

The objects we add to the map item are not regular graphics items, so the possibilities for user interaction with them is limited. However, they do provide a basic selection feature and have features that are useful in their own right. For example, their positions are automatically updated if the map is panned and we can monitor their visibility and positions using the selectedChanged() visibilityChanged() signals.

Just as with graphics items, each specialized type of map object also provides its own properties and functions to make it more convenient to use. Map objects can also be removed from the map item.

The above code is part of a complete example which can be found in the graphicsmaps directory of the examples archive accompanying this article.

Landmarks

When annotating maps with interesting features it soon becomes necessary to find a way to keep track of where these features are located. We might be tempted to create our own mechanism for storing locations, but the QtLocation module provides an API that we can use to store and retrieve locations, called landmarks, and even allows us to categorize them.

Landmarks are accessed via an instance of QLandmarkManager, a class which is responsible for storing and retrieving QLandmark instances. Landmarks themselves contain more than just a location; they can also contain information about the landmark itself, such as its name and a description. Landmarks can be created from scratch and given to the manager to be looked after, but they are often imported from external sources.

To start working with landmarks, we simply need to construct a landmark manager:

  1. QLandmarkManager *landmarkManager = new QLandmarkManager();

 

It is worth noting that the landmark manager provides an interface to a persistent datastore that is shared between applications, so adding new landmarks to an instance of the manager will cause them to be made available to all applications that use the landmarks API. It also means that we do not have to import a list of landmarks each time we construct a QLandmarkManager instance.

The following code snippet from the landmarks example which accompanies this article shows the simplest way in which individual landmarks can be obtained from the manager, first removing any existing map objects from the map item before iterating over the available landmarks, creating a QGeoMapPixmapObject object for each landmark in the datastore:

landmarks

Landmarks.

  1. void MapWindow::updateLandmarks()
  2. {
  3.     landmarkCombo->clear();
  4.     foreach (QGeoMapObject *obj, geoMap->mapObjects())
  5.         geoMap->removeMapObject(obj);
  6.  
  7.     foreach (QLandmark landmark, landmarkManager->landmarks()) {
  8.  
  9.         landmarkCombo->addItem(landmark.name());
  10.         QGeoMapPixmapObject *pixmapObj = new QGeoMapPixmapObject();
  11.         pixmapObj->setCoordinate(landmark.coordinate());
  12.         pixmapObj->setOffset(QPoint(-2, -30));
  13.         pixmapObj->setPixmap(QPixmap(":Resources/landmark.png"));
  14.         geoMap->addMapObject(pixmapObj);
  15.     }
  16. }

 

Landmarks can be added to the manager and saved for later by using the saveLandmark() function for individual landmarks or saveLandmarks() for collections of them. In our example, we import collections of landmarks from files instead, using the importLandmarks() function:

  1. void MapWindow::importLandmarks()
  2. {
  3.     // Obtain a file name with a file dialog.
  4.  
  5.     QFile file(path);
  6.     if (!landmarkManager->importLandmarks(&file))
  7.         QMessageBox::warning(this, tr("Landmarks"),
  8.             tr("Failed to import landmarks: \"%1\"").arg(landmarkManager->errorString()));
  9.     else
  10.         updateLandmarks();
  11. }

 

Certain formats for landmark data files are supported by Qt Mobility 1.1, including GPX and LMX formats. QLandmarkManager imports all the landmarks or waypoints it can find from these files, so any error that is reported will correspond to the latest error that occurred. Really, we should use the errorMap() function to obtain a more precise set of error reports and construct a user-readable message to display in a message box.

The full source code for this example is included in the landmarks directory of the examples archive accompanying this article.

Moving Around the Map

As mentioned earlier, the map item we use to display images from the mapping service is designed to be used as a viewport onto a map, although it can also be used to provide static tiles for a larger scene. In this section, we look at a widget that lets the user navigate by dragging the map.

mapwidget

Map Widget.

Developers who have worked with mouse events before will probably know what needs to be done to make such a widget work, so we will not go into detail. However, we can see the basic mechanism by looking at its mousePressEvent() and mouseMoveEvent() handler functions.

  1. void MapWidget::mousePressEvent(QMouseEvent *event)
  2. {
  3.     if (event->button() == Qt::LeftButton) {
  4.         dragging = true;
  5.         dragStartPosition = event->pos();
  6.         setCursor(Qt::ClosedHandCursor);
  7.         event->accept();
  8.     }
  9. }

 

Handling a mouse press on the widget is simple because we only want to support dragging. When the left mouse button is pressed, we assume that the map will be dragged, so we set an internal flag, record the mouse position, set the cursor shape and accept the event. The other mouse buttons are ignored.

  1. void MapWidget::mouseMoveEvent(QMouseEvent *event)
  2. {
  3.     if (dragging) {
  4.         QPoint v = event->pos() - dragStartPosition;
  5.         geoMap->pan(-v.x(), -v.y());
  6.         dragStartPosition = event->pos();
  7.         event->accept();
  8.     }
  9. }

 

We handle dragging movements in the mouse move event handler. The changes to the mouse's x and y coordinates are negated and passed to the map's pan() function because a displacement of the map in one direction requires that the origin of the map is displaced in the opposite direction.

The above source code is part of the example located in the mapwidget directory of the examples archive accompanying this article.

Declarative Maps

The QtLocation module also includes support for QML, providing a number of ready-made elements in the Location QML plugin. These cover many of the same features as the C++ classes, but expose APIs that are more suitable for use in a declarative language.

displymap

Display Map.

For simply displaying maps, we can use the Map item from the QtMobility.location module. We can configure the item in the following way:

  1. import QtQuick 1.0
  2. import QtMobility.location 1.1
  3.  
  4. Item {
  5.     width: 400; height: 400
  6.  
  7.     Map {
  8.         anchors.fill: parent
  9.         zoomLevel: 7
  10.         center: Coordinate {
  11.             latitude: 51; longitude: 4
  12.         }
  13.         plugin: Plugin {
  14.             name: "nokia"
  15.         }
  16.     }
  17. }

 

As with the C++ code that uses the QGraphicsGeoMap class, we need a way to select which service provider plugin to use, and this is declared using the Plugin element which, in the above example, is set to use the nokia provider. As a result, the above QML can be run in the qmlviewer tool without additional configuration or embedded in a minimal C++ application.

Making a draggable QML item like the C++ map widget described earlier is simpler than you might imagine. The only changes that we need to make is to give the Map item an identifier so that the location it shows can be changed and declare a MouseArea item as a child of the root Item in the user interface, using anchors to cover the same screen area as the Map item.

map with slider

Map with slider.

  1.     MouseArea {
  2.         id: area
  3.         anchors.fill: parent
  4.         property real startX: 0
  5.         property real startY: 0
  6.  
  7.         onPressed: {
  8.             startX = mouse.x
  9.             startY = mouse.y
  10.         }
  11.  
  12.         onPositionChanged: {
  13.             var dx = mouse.x - startX
  14.             var dy = mouse.y - startY
  15.             var pos = map.toScreenPosition(map.center)
  16.             pos.x -= dx
  17.             pos.y -= dy
  18.             map.center = map.toCoordinate(pos)
  19.             startX = mouse.x
  20.             startY = mouse.y
  21.         }
  22.     }

 

As with the C++ widget, the mouse area handles mouse clicks and updates the Map item accordingly.

QML is intended to be used to quickly create user interfaces, and we can easily add simple and familiar controls to the map, but this takes us into the area of QML programming and away from the subject of mapping. Support for mapping using QML is documented as incomplete in Qt Mobility 1.1, with further refinements expected in upcoming releases. In the meantime, there are plenty of items to try out and experiment with.

Three QML examples are included in the QML directory of the examples archive accompanying this article. The map-with-slider.qml example uses the simple controls mentioned above.

Charting a Course

In our quest to reveal the mapping features of Qt Mobility, we have only scratched the surface of the QtLocation module. For example, although we have mentioned the use of map objects to annotate maps, we have not looked at routes and how they are represented or the use of search and routing services. Similarly, we have only covered the simplest uses of landmarks, without even beginning to explore the concepts of categories and filters.

Another area of interest is the extensibility of the mapping engine itself. All the classes that describe the interface used to access services are exposed, meaning that plugins for alternative services can also be written in the same way as the default nokia service provider plugin.

These topics are covered in the Location overview in the Qt Mobility documentation.

David Boddie is a technical writer at Qt Development Frameworks.