Add the application logic

The last part of our app is adding some C++ logic to follow the device location. First, we need to define some functions and variables in our applicationui.hpp file. The applicationui.hpp file is located in the src folder of your project.

We need to add the Maps and Geo namespaces and classes in our app. Go ahead and modify the code in the applicationui.hpp file to included these namespaces:

namespace bb 
{
    namespace cascades 
    {
        class Application;
    	class LocaleHandler;
        namespace maps 
        {
            class MapView;
        }
    }
    namespace platform 
    {
        namespace geo 
        {
            class GeoLocation;
        }
    }
}

Next, add the invokable functions to manage the pins and updating the device location:

public:
 
    Q_INVOKABLE void addPinAtCurrentMapCenter();
    Q_INVOKABLE void clearPins();
    Q_INVOKABLE void updateDeviceLocation(double lat, double lon);

Finally, add two private declarations for our MapView and the device location:

private:

    bb::cascades::maps::MapView* mapView;
    bb::platform::geo::GeoLocation* deviceLocation;

Your applicationui.hpp file is complete.

#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>

namespace bb
{
    namespace cascades
    {
    	class Application;
    	class LocaleHandler;
        namespace maps
        {
            class MapView;
        }
    }
    namespace platform
    {
        namespace geo
        {
            class GeoLocation;
        }
    }
}

class QTranslator;

/*!
 * @brief Application object
 *
 *
 */

class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI(bb::cascades::Application *app);
    virtual ~ApplicationUI() { }
    Q_INVOKABLE void addPinAtCurrentMapCenter();
    Q_INVOKABLE void clearPins();
    Q_INVOKABLE void updateDeviceLocation(double lat, double lon);
private slots:
    void onSystemLanguageChanged();
private:
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;
    bb::cascades::maps::MapView* mapView;
    bb::platform::geo::GeoLocation* deviceLocation;
};

#endif /* ApplicationUI_HPP_ */

In applicationui.cpp, we still need to implement the functions that we declared in the header. Add the include statements and using directives as follows:

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/Container>
#include <bb/cascades/maps/MapView>
#include <bb/cascades/maps/MapData>
#include <bb/cascades/maps/DataProvider>
#include <bb/cascades/QmlDocument>
#include <bb/platform/geo/Point>
#include <bb/platform/geo/GeoLocation>
#include <bb/platform/geo/Marker>
#include <bb/UIToolkitSupport>
#include <bb/cascades/LocaleHandler>

using namespace bb;
using namespace bb::cascades;
using namespace bb::cascades::maps;
using namespace bb::platform::geo;

We add a context property so that we can access our MapView from our QML code:

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app)
{
    // prepare the localization
    // ... 

    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml")
    	.parent(this);

    // Set a context property to access in the QML.
    qml->setContextProperty("_mapViewTest", this);

Next, we set up some code to add a special location to our MapView. We use an if statement to find the MapView object and create a new DataProvider and GeoLocation for the device's current location. We customize a Marker for this location on our map. Go ahead and add the following code to the constructor in your applicationui.cpp file:

    // create root object for the UI
      AbstractPane *root = qml->createRootObject<AbstractPane>();

      QObject* mapViewAsQObject =
    		  root->findChild<QObject*>(QString("mapViewObj"));
      if (mapViewAsQObject) {
          mapView =
        	qobject_cast<bb::cascades::maps::MapView*>(mapViewAsQObject);
          
          if (mapView) {
                
              mapView->setCaptionGoButtonVisible(true);

              // Create a data provider just for the device location
        	  // object. That way, when the clear function is called,
        	  // this object is not removed.
              DataProvider* deviceLocDataProv =
            	new DataProvider("device-location-data-provider");
              mapView->mapData()->addProvider(deviceLocDataProv);

              // Create a geolocation just for the device's location.
              deviceLocation = new GeoLocation("device-location-id");
              deviceLocation->setName("Current Device Location");
              deviceLocation->setDescription("<html><a href=\"http://www.blackberry.com\">Hyperlinks</a> are super useful in bubbles.</html>");

              // For that location, replace the standard default
              // pin with the provided bulls eye asset.
              Marker bullseye = Marker(UIToolkitSupport::absolutePathFromUrl(
                 QUrl("asset:///images/me.png")), QSize(60, 60),
                 QPoint(29, 29), QPoint(29, 1));
              deviceLocation->setMarker(bullseye);

              deviceLocDataProv->add(deviceLocation);
          }
      }

      // set created root object as a scene
      app->setScene(root);
}

Now we need to implement the three functions that we declared in our application.hpp file.

Add the addPinAtCurrentMapCenter() function. This function creates a GeoLocation object that is visually represented at the center of the MapView. We set the name and description of this GeoLocation, and we customize the marker with our own marker image and caption. Then, we add the marker to the GeoLocation using the setMarker() function.

void ApplicationUI::addPinAtCurrentMapCenter() {
	if (mapView) {
		GeoLocation* newDrop = new GeoLocation();
		newDrop->setLatitude(mapView->latitude());
		newDrop->setLongitude(mapView->longitude());
		QString desc =
		  QString("Coordinates: %1, %2").arg(mapView->latitude(),
			0, 'f', 3).arg(mapView->longitude(), 0, 'f', 3);
		newDrop->setName("Dropped Pin");
		newDrop->setDescription(desc);

		// Use the marker in the assets, as opposed to
		// the default marker
		Marker flag;
		flag.setIconUri(UIToolkitSupport::absolutePathFromUrl(
						QUrl("asset:///images/on_map_pin.png")));
		flag.setIconSize(QSize(60, 60));
		flag.setLocationCoordinate(QPoint(20, 59));
		flag.setCaptionTailCoordinate(QPoint(20, 1));
		newDrop->setMarker(flag);

		mapView->mapData()->add(newDrop);
	}
}

We also need to implement the clearPins() function. Our current MapView includes a mapData property, which represents the data providers for the map. We use the clear() function of the default data provider, defaultProvider, to clear the pins from our map.

Earlier, we created a separate data provider for a special marker on our MapView so that the clearPins() function doesn't clear the user's current location from the MapView.

void ApplicationUI::clearPins() {
	if (mapView) {
		// This will remove all pins, except the "device location"
		// pin, since it's in a different data provider
		mapView->mapData()->defaultProvider()->clear();
	}
}

Finally, we need to implement the updateDeviceLocation() function. This function is invoked when the position of our device changes. Remember that, in our main.qml file, we attached a PositionSource to our main UI container to represent the user's current location. This function updates the latitude and longitude of our device's location on the MapView.

void ApplicationUI::updateDeviceLocation(double lat, double lon) {
    if (mapView) {
	    qDebug() << "ApplicationUI::updateDeviceLocation("
			" " << lat << ", " << lon << " )";
	    if (deviceLocation) {
	    	deviceLocation->setLatitude(lat);
		    deviceLocation->setLongitude(lon);
	    }
	    mapView->setLatitude(lat);
	    mapView->setLongitude(lon);
    }
}

Your applicationui.cpp file is complete.

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/Container>
#include <bb/cascades/maps/MapView>
#include <bb/cascades/maps/MapData>
#include <bb/cascades/maps/DataProvider>
#include <bb/cascades/QmlDocument>
#include <bb/platform/geo/Point>
#include <bb/platform/geo/GeoLocation>
#include <bb/platform/geo/Marker>
#include <bb/UIToolkitSupport>
#include <bb/cascades/LocaleHandler>

using namespace bb;
using namespace bb::cascades;
using namespace bb::cascades::maps;
using namespace bb::platform::geo;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app)
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);
    if(!QObject::connect(m_pLocaleHandler, SIGNAL(systemLanguageChanged()), this, SLOT(onSystemLanguageChanged()))) {
        // This is an abnormal situation! Something went wrong!
        // Add own code to recover here
        qWarning() << "Recovering from a failed connect()";
    }
    // initial load
    onSystemLanguageChanged();

    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml")
    	.parent(this);

    // Set a context property to access in the QML.
    qml->setContextProperty("_mapViewTest", this);

    // create root object for the UI
      AbstractPane *root = qml->createRootObject<AbstractPane>();

      QObject* mapViewAsQObject =
    		  root->findChild<QObject*>(QString("mapViewObj"));
      if (mapViewAsQObject) {
          mapView =
        	qobject_cast<bb::cascades::maps::MapView*>(mapViewAsQObject);
          mapView->setCaptionGoButtonVisible(true);
          if (mapView) {

              // Create a data provider just for the device location
        	  // object. That way, when the clear function is called,
        	  // this object is not removed.
              DataProvider* deviceLocDataProv =
            	new DataProvider("device-location-data-provider");
              mapView->mapData()->addProvider(deviceLocDataProv);

              // Create a geolocation just for the device's location.
              deviceLocation = new GeoLocation("device-location-id");
              deviceLocation->setName("Current Device Location");
              deviceLocation->setDescription("<html><a href=\"http://www.blackberry.com\">Hyperlinks</a> are super useful in bubbles.</html>");

              // For that location, replace the standard default
              // pin with the provided bulls eye asset.
              Marker bullseye = Marker(UIToolkitSupport::absolutePathFromUrl(
                 QUrl("asset:///images/me.png")), QSize(60, 60),
                 QPoint(29, 29), QPoint(29, 1));
              deviceLocation->setMarker(bullseye);

              deviceLocDataProv->add(deviceLocation);
          }
      }

      // set created root object as a scene
      app->setScene(root);
}

void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("Mapview_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

void ApplicationUI::addPinAtCurrentMapCenter() {
	if (mapView) {
		GeoLocation* newDrop = new GeoLocation();
		newDrop->setLatitude(mapView->latitude());
		newDrop->setLongitude(mapView->longitude());
		QString desc =
		  QString("Coordinates: %1, %2").arg(mapView->latitude(),
			0, 'f', 3).arg(mapView->longitude(), 0, 'f', 3);
		newDrop->setName("Dropped Pin");
		newDrop->setDescription(desc);

		// Use the marker in the assets, as opposed to
		// the default marker
		Marker flag;
		flag.setIconUri(UIToolkitSupport::absolutePathFromUrl(
						QUrl("asset:///images/on_map_pin.png")));
		flag.setIconSize(QSize(60, 60));
		flag.setLocationCoordinate(QPoint(20, 59));
		flag.setCaptionTailCoordinate(QPoint(20, 1));
		newDrop->setMarker(flag);

		mapView->mapData()->add(newDrop);
	}
}

void ApplicationUI::clearPins() {
	if (mapView) {
		// This will remove all pins, except the "device location"
		// pin, since it's in a different data provider
		mapView->mapData()->defaultProvider()->clear();
	}
}

void ApplicationUI::updateDeviceLocation(double lat, double lon) {
	qDebug() << "ApplicationUI::updateDeviceLocation("
			" " << lat << ", " << lon << " )";
	if (deviceLocation) {
		deviceLocation->setLatitude(lat);
		deviceLocation->setLongitude(lon);
	}
	mapView->setLatitude(lat);
	mapView->setLongitude(lon);
}

We're done! Build and run the app to see the results.

For other maps examples, check out these samples:

  • Custom MapView: This app demonstrates how to use layers to display traffic, pins, and markers using overlays.
  • Web MapView: This app demonstrates how to build maps from different providers into your app.
Screen showing the MapView sample with the marker of the current device location touched on the map.

Last modified: 2013-12-21

comments powered by Disqus