Tutorial 1: Write a simple mapping application using the new QGIS Canvas API

Ok so not everyone wants a full blown GIS desktop application. Sometimes you want to just have a widget inside your application that displays a map while the main goal of the application lies elsewhere. Perhaps a database frontend with a map display?

So lets take a little walk through creating a simple mapping widget. It wont do anything much - just load a shape file and display it in a random colour. But it should give you an idea of the potential for using QGIS as an embedded mapping component. Before I carry on, many thanks to Francis Bolduc who wrote the beginnings of this demo and then came onto IRC today to do some communal debugging of his prototype. Francis kindly agreed to make his work generally available - though I've tweaked it a bit to use shapefiles instead of his original postgis demo.

We start with typical adding the neccessary includes for our app:

//
// QGIS Includes
//
#include <qgsapplication.h>
#include <qgsproviderregistry.h>
#include <qgssinglesymbolrenderer.h>
#include <qgsmaplayerregistry.h>
#include <qgsvectorlayer.h>
#include <qgsmapcanvas.h>
//
// Qt Includes
//
#include <QString>
#include <QApplication>
#include <QWidget>
//
// Std Includes
//
#include <deque.h>
#include <iostream>

We use qgsapplication instead of Qt's QApplication, and get some added benifits of various static methods that can be used to locate library paths and so on.

The provider registry is a singleton that keeps track of vector data provider plugins. It does all the work for you of loading the plugins and so on. The single symbol renderer is the most basic symbology class. It renders points, lines or polygons in a single colour which is chosen at random by default (though you can set it yourself). Every vector layer must have a symbology associated with it.

The map layer registry keeps track of all the layers you are using. The vector layer class inherits from maplayer and extends it to include specialist functionality for vector data.

Finally the mapcanvas is really the nub of the matter. Its the drawable widget that our map will be drawn onto.

Now we can move on to initialising our application....

int main(int argc, char ** argv) 
{
  // Start the Application
  QgsApplication app(argc, argv, true);

  QString myPluginsDir        = "/home/timlinux/apps/lib/qgis";
  QString myLayerPath         = "/home/timlinux/gisdata/brazil/BR_Cidades/";
  QString myLayerBaseName     = "Brasil_Cap";
  QString myProviderName      = "ogr";

So now we have a qgsapplication and we have defined some variables. Since I tested this on the mac, I just specified the location of the vector provider plugins as being inside the qgis.app application bundle. It would probaby make more sense in general to keep the qgis libs in one of the standard library search paths on your system (e.g. /usr/lib) but this way will do for now.

The next two variables defined here just point to the shapefile I am going to be using.

The provider name is important - it tells qgis which data provider to use to load the file. Typically you will use 'ogr' or 'postgres'.

Now we can go on to actually create our layer object.

  // Instantiate Provider Registry
  QgsProviderRegistry::instance(myPluginsDir);

First we get teh provider registry initialised. Its a singleton class so we use the static instance call and pass it the provider lib search path. As it initialises it will scan this path for provider libs.

Now we go on to create a layer...

  QgsVectorLayer * mypLayer = 
      new QgsVectorLayer(myLayerPath, myLayerBaseName, myProviderName);
  QgsSingleSymbolRenderer *mypRenderer = new QgsSingleSymbolRenderer(mypLayer->vectorType());
  std::deque<QString> myLayerSet;
  mypLayer->setRenderer(mypRenderer);
  if (mypLayer->isValid())
  {
    qDebug("Layer is valid");
  }
  else
  {
    qDebug("Layer is NOT valid");
  }

  // Add the Vector Layer to the Layer Registry
  QgsMapLayerRegistry::instance()->addMapLayer(mypLayer, TRUE);
  // Add the Layer to the Layer Set
  myLayerSet.push_back(mypLayer->getLayerID());
  mypLayer->setVisible(TRUE);

The code is fairly self explanatory here. We create a layer using the variables we defined earlier. Then we assign the layer a renderer. Next we add it to a layerset (which is used by the mapcanvas to keep track of which layers to render and in what order) and to the maplayer registry. Finally we make sure the layer will be visible.

Now we create a map canvas on to which we can draw the layer.

  // Create the Map Canvas
  QgsMapCanvas * mypMapCanvas = new QgsMapCanvas(0, 0);
  mypMapCanvas->setExtent(mypLayer->extent());
  qDebug(mypMapCanvas->extent().stringRep(2));
  mypMapCanvas->enableAntiAliasing(true);
  mypMapCanvas->setCanvasColor(QColor(255, 255, 255));
  mypMapCanvas->freeze(false);
  // Set the Map Canvas Layer Set
  mypMapCanvas->setLayerSet(myLayerSet);
  mypMapCanvas->setVisible(true);
  mypMapCanvas->refresh();

Once again there is nothing particularly tricky here. We create the canvas and then we set its extents to those of our layer. Next we tweak the canvas a bit to draw antialiased vectors. Next we set the background colour, unfreeze the canvas, make it visible and then refresh it.

  // Start the Application Event Loop
  return app.exec();
}

In the last step we simply start the Qt event loop and we are all done. Easy as 1-2-3 eh? in future posts Ill try to do other examples for using raster layers, wms layers and postgres....

When we compile and run it heres what the running app looks like:

Once again many thanks to Francis for prompting this posting.

AttachmentSize
libqgis_minimal_example.jpg11.26 KB

Reply