In my last tutorial I showed you how to add a vector attribute data. Today we will take a first look at creating custom maptools. First lets take a sneak preview of what we will achieve in this tutorial. Heres a screen shot: We will implement a simple map tool that will allow the user to click on the canvas and 'drill' through a bunch of raster layers and produce a table of layer names and their values at the point where we clicked. I used as a basis for this tutorial the project from tutorial 4 which covered loading rasters, so its worth going over that before reading this tutorial. I also suggest looking at the other tutorials before going through this one so you can get a but more familiar with the canvas API - I won't be rehashing that here.

As with my previous tutorials, the entire project can be checked out from the QGIS Subversion repository using the following command:

svn co https://svn.qgis.org/repos/qgis/trunk/code_examples/7_writing_custom_maptools

In the working directory for the tutorial code you will find a number of files including c++ sources, icons and a simple data file under data. There is also the .ui file for the main window.

Note: You will need to edit the .pro file in the above svn directory and adjust the paths to match your system.

Map Tool Specifics

You can browse the code for QgsMapTool and related classes using the QGIS source browser. Look in the 'gui' and 'core' subdirectories. The classes we are particularly interested in are QgsMapTool, QgsMapLayer, QgsMapToPixel and QgsMapCanvas. Also for more implementation examples, take a look at qgsmeasure.cpp. Now on with our demo application.... The procedure for creating a custom map tool is simple - create a subclass of QgsMapTool and implement your custom behaviour there. Then simply add your map tool to your application as I have described in Tutorial 2: Using MapTools with the QGIS Canvas API.

Creating the custom MapTool

Ok so lets start by creating our custom map tool. First we create maptooldriller.h :

You will notice that we inherit from QgsMapTool - this is mandatory. Then we declare the methods that will implement the tool behaviour. In this case we want to invoke our logic when the mouse is released on the mapcanvas. In mainwindow.h you will notice the include needed to work with bands:
    #include <qgsrubberband.h>
Next I added a couple of slots to handle events from the show and hide rubber band tool buttons that I added to the main window:
  // next are tools overloaded from base class
  void on_mpToolShowRubberBand_clicked();
  void on_mpToolHideRubberBand_clicked();
Also I've added a class member for our rubber band:
  QgsRubberBand * mpRubberBand;
All pretty straightforward so far. Lets go over to the implementation file, mainwindow.cpp. First you will see a new include:
    #include <qgspoint.h>
This is needed because we will be using QgsPoint objects to define the vertices of our rubber band. The code that follows sets up the main window and toolbars as covered in previous tutorials. Towards the end of the constructor you will see we initialise the rubber band member:
 
     //create the rubber band
     bool myPolygonFlag=true;
     mpRubberBand = new QgsRubberBand(mpMapCanvas, myPolygonFlag );
     mpRubberBand->show();
I've written it a bit more verbosely than necessary to make it clear. The rubber band constructor needs two parameters; the QgsMapCanvas on to which our rubber band will be drawn, and a boolean indicating whether the rubber band is a polygon or not. In the case that the rubber band is a polygon it will always close the gap between first and last points. Next I wired up the two toolButtons I added using Qt4 Designer to the mainwindowbase.ui file. Lets look at the show event first:
void MainWindow::on_mpToolShowRubberBand_clicked()
{
  QgsPoint myPoint1 = mpMapCanvas->getCoordinateTransform()->toMapCoordinates(10, 10);
  mpRubberBand->addPoint(myPoint1);
  QgsPoint myPoint2 = mpMapCanvas->getCoordinateTransform()->toMapCoordinates(20, 10);
  mpRubberBand->addPoint(myPoint2);
  QgsPoint myPoint3 = mpMapCanvas->getCoordinateTransform()->toMapCoordinates(20, 20);
  mpRubberBand->addPoint(myPoint3);
  QgsPoint myPoint4 = mpMapCanvas->getCoordinateTransform()->toMapCoordinates(10, 20);
  mpRubberBand->addPoint(myPoint4);
}
We are specifying here to draw a 10x10 pixel box, starting its top left corner at 10,10 pixels from the corner of the map canvase. So whats all that coordinate transform stuff about then? The rubber band operates in the spatial reference system of the associated canvas. In other words when you specify the coordinates of vertices it should be in map units, not in screen pixel coordinates. Fortunately there is an easy way to translate from screen coordinates to map coordinates. First we get the QgsMapToPixel instance from the map canvas. Next we call its toMapCoordinates(int,int) method which will return a QgsPoint. For each point I create I am just adding it to the rubber band. Because I am walking around the corners of a square, the end result will be a square drawn on screen. Remember that we specified our rubber band is a polygon in the constructor, so there is no need to create a 5th point to close the polygon - its automaticall closed for us! Now that we know how to create a QgsRubberBand, we should also show how to remove it from the canvas again. This is illustrated in the next method:
void MainWindow::on_mpToolHideRubberBand_clicked()
{
  bool myPolygonFlag=true;
  mpRubberBand->reset(myPolygonFlag);
}
Ok so thats pretty easy - just call reset on the rubber band. When you do so you need to respecify whether the rubber band is to operate in polyline or polygon mode. Lastly you should note that you are responsible for managing the memory allocated to the rubber band, this you will see that in the destructor for mainwindow.cpp I have the following:
delete mpRubberBand;
You are not limited to only one QgsRubberBand instance either - you can add as many as you like. In the screenshot below I added a second rubber band: Well I hope you find lots of cool uses for QgsRubberBand - feel free to let us know in the comments below how you are using it. Thats the end of tutorial5!

Mac Specific Notes

After building the application bundle (qmake; make) you can copy the spatial reference system database into the bundle to avoid 'cant find resource' type errors:
mkdir -p qgis_example3.app/Contents/MacOS/share/qgis/resources/ 
cp -r /Applications/qgis.app/Contents/MacOS/share/qgis/resources/* \ 
         qgis_example3.app/Contents/MacOS/share/qgis/resources/