メインページ/QGIS/coding-compilation guide/plugins writing in cpp/

提供: OSGeo.JP Wiki
2009年8月25日 (火) 19:33時点におけるKayama (トーク | 投稿記録)による版

(差分) ←前の版 | 最新版 (差分) | 次の版→ (差分)
移動: 案内検索
% vim: set textwidth=78 autoindent:
 
\section{Writing a QGIS Plugin in C++}\label{cpp_plugin}
 
% when the revision of a section has been finalized, 
% comment out the following line:
% \updatedisclaimer
 
In this section we provide a beginner's tutorial for writing a simple QGIS
C++ plugin. It is based on a workshop held by Dr. Marco Hugentobler. 
 
QGIS C++ plugins are dynamically linked libraries (.so or .dll). They are
linked to QGIS at runtime when requested in the Plugin Manager, and extend the
functionality of QGIS via access to the QGIS GUI. In general, they can be devided
into core and external plugins.
 
Technically the QGIS Plugin Manager looks in the lib/qgis directory for all
.so files and loads them when it is started. When it is closed they are
unloaded again, except the ones enabled by the user (See User Manual). For newly loaded plugins,
the \method{classFactory} method creates an instance of the plugin class and
the \method{initGui} method of the plugin is called to show the GUI elements
in the plugin menu and toolbar. The \method{unload()} function of the plugin
is used to remove the allocated GUI elements and the plugin class itself is
removed using the class destructor. To list the plugins, each plugin must
have a few external 'C' functions for description and of course the
\method{classFactory} method.
 
\subsection{Why C++ and what about licensing}
 
QGIS itself is written in C++, so it makes sense to write plugins in C++
as well. It is an object-oriented programming (OOP) language that is prefered 
by many developers for creating large-scale applications.
 
QGIS C++ plugins take advantage of the functionalities provided by the libqgis*.so libraries. 
As these libraries licensed under the GNU GPL, QGIS C++ plugins must also be licenced under 
the GPL. This means that you may use your plugins for any purpose and you are not required to
publish them. If you do publish them however, they must be published under
the conditions of the GPL license. 
 
\subsection{Programming a QGIS C++ Plugin in four steps}
 
The C++ plugin example covered in this manual is a point converter plugin and intentionally kept simple. 
The plugin searches the active vector layer in QGIS, converts all vertices of 
the layer's features to point features (keeping the attributes), and finally
writes the point features to a delimited text file. The new layer can then
be loaded into QGIS using the delimited text plugin (see User Manual).
 
\minisec{Step 1: Make the plugin manager recognise the plugin}
 
As a first step we create the \filename{QgsPointConverter.h} and
\filename{QgsPointConverter.cpp} files. We then add virtual methods inherited
from QgisPlugin (but leave them empty for now), create the necessary external 'C'
methods, and a .pro file (which is a Qt mechanism to easily create Makefiles).
Then we compile the sources, move the compiled library into the plugin folder,
and load it in the QGIS Plugin Manager.
 
\textbf{a) Create new pointconverter.pro file and add}:
 
 
% 
%
% Note: the use of qmake / pro files for building plugins 
% is outdated and the reader should be made aware that this 
% chapter is not longer current. Tim will update this 
% chapter in the near future, but in the meantime this
% note serves as a reminder.
%
%
 
 
\begin{verbatim}
#base directory of the qgis installation
QGIS_DIR = /home/marco/src/qgis
 
TEMPLATE = lib
CONFIG = qt
QT += xml qt3support
unix:LIBS += -L/$$QGIS_DIR/lib -lqgis_core -lqgis_gui
INCLUDEPATH += $$QGIS_DIR/src/ui $$QGIS_DIR/src/plugins  $$QGIS_DIR/src/gui \
	       $$QGIS_DIR/src/raster $$QGIS_DIR/src/core $$QGIS_DIR 
SOURCES = qgspointconverterplugin.cpp
HEADERS = qgspointconverterplugin.h
DEST = pointconverterplugin.so
DEFINES += GUI_EXPORT= CORE_EXPORT=
\end{verbatim}
 
\textbf{b) Create new qgspointconverterplugin.h file and add}:
 
\begin{verbatim}
#ifndef QGSPOINTCONVERTERPLUGIN_H
#define QGSPOINTCONVERTERPLUGIN_H
 
#include "qgisplugin.h"
 
/**A plugin that converts vector layers to delimited text point files.
 The vertices of polygon/line type layers are converted to point features*/
class QgsPointConverterPlugin: public QgisPlugin
{
  public:
  QgsPointConverterPlugin(QgisInterface* iface);
  ~QgsPointConverterPlugin();
  void initGui();
  void unload();
 
  private:
  QgisInterface* mIface;
};
#endif
\end{verbatim}
 
\textbf{c) Create new qgspointconverterplugin.cpp file and add}:
 
\begin{verbatim}
#include "qgspointconverterplugin.h"
 
#ifdef WIN32
#define QGISEXTERN extern "C" __declspec( dllexport )
#else
#define QGISEXTERN extern "C"
#endif
 
QgsPointConverterPlugin::QgsPointConverterPlugin(QgisInterface* iface): mIface(iface)
{
}
 
QgsPointConverterPlugin::~QgsPointConverterPlugin()
{
}
 
void QgsPointConverterPlugin::initGui()
{
}
 
void QgsPointConverterPlugin::unload()
{
}
 
QGISEXTERN QgisPlugin* classFactory(QgisInterface* iface)
{
  return new QgsPointConverterPlugin(iface);
}
 
QGISEXTERN QString name()
{
  return "point converter plugin";
}
 
QGISEXTERN QString description()
{
  return "A plugin that converts vector layers to delimited text point files";
}
 
QGISEXTERN QString version()
{
  return "0.00001";
}
 
// Return the type (either UI or MapLayer plugin)
QGISEXTERN int type()
{
  return QgisPlugin::UI;
}
 
// Delete ourself
QGISEXTERN void unload(QgisPlugin* theQgsPointConverterPluginPointer)
{
  delete theQgsPointConverterPluginPointer;
}
\end{verbatim}
 
\minisec{Step 2: Create an icon, a button and a menu for the plugin}
 
This step includes adding a pointer to the QgisInterface object in the plugin
class. Then we create a QAction and a callback function (slot), add it to the
QGIS GUI using QgisInterface::addToolBarIcon() and QgisInterface::addPluginToMenu()
and finally remove the QAction in the \method{unload()} method.
 
\textbf{d) Open qgspointconverterplugin.h again and extend existing content to}:
 
\begin{verbatim}
#ifndef QGSPOINTCONVERTERPLUGIN_H
#define QGSPOINTCONVERTERPLUGIN_H
 
#include "qgisplugin.h"
#include <QObject>
 
class QAction;
 
/**A plugin that converts vector layers to delimited text point files.
 The vertices of polygon/line type layers are converted to point features*/
class QgsPointConverterPlugin: public QObject, public QgisPlugin
{
  Q_OBJECT
 
 public:
  QgsPointConverterPlugin(QgisInterface* iface);
  ~QgsPointConverterPlugin();
  void initGui();
  void unload();
 
 private:
  QgisInterface* mIface;
  QAction* mAction;
 
   private slots:
   void convertToPoint();
};
 
#endif
\end{verbatim}
 
\textbf{e) Open qgspointconverterplugin.cpp again and extend existing content to}:
 
\begin{verbatim}
#include "qgspointconverterplugin.h"
#include "qgisinterface.h"
#include <QAction>
 
#ifdef WIN32
#define QGISEXTERN extern "C" __declspec( dllexport )
#else
#define QGISEXTERN extern "C"
#endif
 
QgsPointConverterPlugin::QgsPointConverterPlugin(QgisInterface* iface): \
    mIface(iface), mAction(0)
{
 
}
 
QgsPointConverterPlugin::~QgsPointConverterPlugin()
{
 
}
 
void QgsPointConverterPlugin::initGui()
{
  mAction = new QAction(tr("&Convert to point"), this);
  connect(mAction, SIGNAL(activated()), this, SLOT(convertToPoint()));
  mIface->addToolBarIcon(mAction);
  mIface->addPluginToMenu(tr("&Convert to point"), mAction);
}
 
void QgsPointConverterPlugin::unload()
{
  mIface->removeToolBarIcon(mAction);
  mIface->removePluginMenu(tr("&Convert to point"), mAction);
  delete mAction;
}
 
void QgsPointConverterPlugin::convertToPoint()
{
  qWarning("in method convertToPoint");
}
 
QGISEXTERN QgisPlugin* classFactory(QgisInterface* iface)
{
  return new QgsPointConverterPlugin(iface);
}
 
QGISEXTERN QString name()
{
  return "point converter plugin";
}
 
QGISEXTERN QString description()
{
  return "A plugin that converts vector layers to delimited text point files";
}
 
QGISEXTERN QString version()
{
  return "0.00001";
}
 
// Return the type (either UI or MapLayer plugin)
QGISEXTERN int type()
{
  return QgisPlugin::UI;
}
 
// Delete ourself
QGISEXTERN void unload(QgisPlugin* theQgsPointConverterPluginPointer)
{
  delete theQgsPointConverterPluginPointer;
}
\end{verbatim}
 
 
\minisec{Step 3: Read point features from the active layer and write to text file}
 
To read the point features from the active layer we need to query the current
layer and the location for the new text file. Then we iterate through all
features of the current layer, convert the geometries (vertices) to points,
open a new file and use QTextStream to write the x- and y-coordinates
into it.
 
\textbf{f) Open qgspointconverterplugin.h again and extend existing content to}
 
\begin{verbatim}
class QgsGeometry;
class QTextStream;
 
private:
 
void convertPoint(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
void convertMultiPoint(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
void convertLineString(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
void convertMultiLineString(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
void convertPolygon(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
void convertMultiPolygon(QgsGeometry* geom, const QString& attributeString, \
		  QTextStream& stream) const;
\end{verbatim}
 
\textbf{g) Open qgspointconverterplugin.cpp again and extend existing content to}:
 
\begin{verbatim}
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QTextStream>
 
void QgsPointConverterPlugin::convertToPoint()
{
  qWarning("in method convertToPoint");
  QgsMapLayer* theMapLayer = mIface->activeLayer();
  if(!theMapLayer)
    {
      QMessageBox::information(0, tr("no active layer"), \
      tr("this plugin needs an active point vector layer to make conversions \ 
          to points"), QMessageBox::Ok);
      return;
    }
  QgsVectorLayer* theVectorLayer = dynamic_cast<QgsVectorLayer*>(theMapLayer);
  if(!theVectorLayer)
    {
      QMessageBox::information(0, tr("no vector layer"), \
      tr("this plugin needs an active point vector layer to make conversions \
          to points"), QMessageBox::Ok);
      return;
    }
 
  QString fileName = QFileDialog::getSaveFileName();
  if(!fileName.isNull())
    {
      qWarning("The selected filename is: " + fileName);
      QFile f(fileName);
      if(!f.open(QIODevice::WriteOnly))
      {
	QMessageBox::information(0, "error", "Could not open file", QMessageBox::Ok);
	return;
      }
      QTextStream theTextStream(&f);
      theTextStream.setRealNumberNotation(QTextStream::FixedNotation);
 
      QgsFeature currentFeature;
      QgsGeometry* currentGeometry = 0;
 
      QgsVectorDataProvider* provider = theVectorLayer->dataProvider();
      if(!provider)
      {
          return;
      }
 
      theVectorLayer->select(provider->attributeIndexes(), \
      theVectorLayer->extent(), true, false);
 
      //write header
      theTextStream << "x,y";
      theTextStream << endl;
 
      while(theVectorLayer->nextFeature(currentFeature))
      {
	 QString featureAttributesString;
 
        currentGeometry = currentFeature.geometry();
        if(!currentGeometry)
        {
            continue;
        }
 
        switch(currentGeometry->wkbType())
        {
            case QGis::WKBPoint:
            case QGis::WKBPoint25D:
                convertPoint(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiPoint:
            case QGis::WKBMultiPoint25D:
                convertMultiPoint(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBLineString:
            case QGis::WKBLineString25D:
                convertLineString(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiLineString:
            case QGis::WKBMultiLineString25D:
                convertMultiLineString(currentGeometry, featureAttributesString \
		theTextStream);
                break;
 
            case QGis::WKBPolygon:
            case QGis::WKBPolygon25D:
                convertPolygon(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiPolygon:
            case QGis::WKBMultiPolygon25D:
                convertMultiPolygon(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
        }
      }
    }
}
 
//geometry converter functions
void QgsPointConverterPlugin::convertPoint(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPoint p = geom->asPoint();
    stream << p.x() << "," << p.y();
    stream << endl;
}
 
void QgsPointConverterPlugin::convertMultiPoint(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPoint mp = geom->asMultiPoint();
    QgsMultiPoint::const_iterator it = mp.constBegin();
    for(; it != mp.constEnd(); ++it)
    {
        stream << (*it).x() << "," << (*it).y();
        stream << endl;
    }
}
 
void QgsPointConverterPlugin::convertLineString(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPolyline line = geom->asPolyline();
    QgsPolyline::const_iterator it = line.constBegin();
    for(; it != line.constEnd(); ++it)
    {
        stream << (*it).x() << "," << (*it).y();
        stream << endl;
    }
}
 
void QgsPointConverterPlugin::convertMultiLineString(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPolyline ml = geom->asMultiPolyline();
    QgsMultiPolyline::const_iterator lineIt = ml.constBegin();
    for(; lineIt != ml.constEnd(); ++lineIt)
    {
        QgsPolyline currentPolyline = *lineIt;
        QgsPolyline::const_iterator vertexIt = currentPolyline.constBegin();
        for(; vertexIt != currentPolyline.constEnd(); ++vertexIt)
        {
            stream << (*vertexIt).x() << "," << (*vertexIt).y();
            stream << endl;
        }
    }
}
 
void QgsPointConverterPlugin::convertPolygon(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPolygon polygon = geom->asPolygon();
    QgsPolygon::const_iterator it = polygon.constBegin();
    for(; it != polygon.constEnd(); ++it)
    {
        QgsPolyline currentRing = *it;
        QgsPolyline::const_iterator vertexIt = currentRing.constBegin();
        for(; vertexIt != currentRing.constEnd(); ++vertexIt)
        {
            stream << (*vertexIt).x() << "," << (*vertexIt).y();
            stream << endl;
        }
    }
}
 
void QgsPointConverterPlugin::convertMultiPolygon(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPolygon mp = geom->asMultiPolygon();
    QgsMultiPolygon::const_iterator polyIt = mp.constBegin();
    for(; polyIt != mp.constEnd(); ++polyIt)
    {
        QgsPolygon currentPolygon = *polyIt;
        QgsPolygon::const_iterator ringIt = currentPolygon.constBegin();
        for(; ringIt != currentPolygon.constEnd(); ++ringIt)
        {
            QgsPolyline currentPolyline = *ringIt;
            QgsPolyline::const_iterator vertexIt = currentPolyline.constBegin();
            for(; vertexIt != currentPolyline.constEnd(); ++vertexIt)
            {
                stream << (*vertexIt).x() << "," << (*vertexIt).y();
                stream << endl;
            }
        }
    }
}
\end{verbatim}
 
\minisec{Step 4: Copy the feature attributes to the text file}
 
At the end we extract the attributes from the active layer using 
QgsVectorDataProvider::fieldNameMap(). For each feature we extract the field 
values using QgsFeature::attributeMap() and add the contents (comma separated) 
behind the x- and y-coordinates for each new point feature. For this step 
there is no need for any furter change in \filename{qgspointconverterplugin.h} 
 
\textbf{h) Open qgspointconverterplugin.cpp again and extend existing content
to}:
 
\begin{verbatim} 
#include "qgspointconverterplugin.h"
#include "qgisinterface.h"
#include "qgsgeometry.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
#include <QAction>
#include <QFileDialog>
#include <QMessageBox>
#include <QTextStream>
 
#ifdef WIN32
#define QGISEXTERN extern "C" __declspec( dllexport )
#else
#define QGISEXTERN extern "C"
#endif
 
QgsPointConverterPlugin::QgsPointConverterPlugin(QgisInterface* iface): \
mIface(iface), mAction(0)
{
 
}
 
QgsPointConverterPlugin::~QgsPointConverterPlugin()
{
 
}
 
void QgsPointConverterPlugin::initGui()
{
  mAction = new QAction(tr("&Convert to point"), this);
  connect(mAction, SIGNAL(activated()), this, SLOT(convertToPoint()));
  mIface->addToolBarIcon(mAction);
  mIface->addPluginToMenu(tr("&Convert to point"), mAction);
}
 
void QgsPointConverterPlugin::unload()
{
  mIface->removeToolBarIcon(mAction);
  mIface->removePluginMenu(tr("&Convert to point"), mAction);
  delete mAction;
}
 
void QgsPointConverterPlugin::convertToPoint()
{
  qWarning("in method convertToPoint");
  QgsMapLayer* theMapLayer = mIface->activeLayer();
  if(!theMapLayer)
    {
      QMessageBox::information(0, tr("no active layer"), \
      tr("this plugin needs an active point vector layer to make conversions \
          to points"), QMessageBox::Ok);
      return;
    }
  QgsVectorLayer* theVectorLayer = dynamic_cast<QgsVectorLayer*>(theMapLayer);
  if(!theVectorLayer)
    {
      QMessageBox::information(0, tr("no vector layer"), \
      tr("this plugin needs an active point vector layer to make conversions \
          to points"), QMessageBox::Ok);
      return;
    }
 
  QString fileName = QFileDialog::getSaveFileName();
  if(!fileName.isNull())
    {
      qWarning("The selected filename is: " + fileName);
      QFile f(fileName);
      if(!f.open(QIODevice::WriteOnly))
      {
	QMessageBox::information(0, "error", "Could not open file", QMessageBox::Ok);
	return;
      }
      QTextStream theTextStream(&f);
      theTextStream.setRealNumberNotation(QTextStream::FixedNotation);
 
      QgsFeature currentFeature;
      QgsGeometry* currentGeometry = 0;
 
      QgsVectorDataProvider* provider = theVectorLayer->dataProvider();
      if(!provider)
      {
          return;
      }
 
      theVectorLayer->select(provider->attributeIndexes(), \
      theVectorLayer->extent(), true, false);
 
      //write header
      theTextStream << "x,y";
      QMap<QString, int> fieldMap = provider->fieldNameMap();
      //We need the attributes sorted by index.
      //Therefore we insert them in a second map where key / values are exchanged
      QMap<int, QString> sortedFieldMap;
      QMap<QString, int>::const_iterator fieldIt = fieldMap.constBegin();
      for(; fieldIt != fieldMap.constEnd(); ++fieldIt)
      {
        sortedFieldMap.insert(fieldIt.value(), fieldIt.key());
      }
 
      QMap<int, QString>::const_iterator sortedFieldIt = sortedFieldMap.constBegin();
      for(; sortedFieldIt != sortedFieldMap.constEnd(); ++sortedFieldIt)
      {
          theTextStream << "," << sortedFieldIt.value();
      }
 
      theTextStream << endl;
 
      while(theVectorLayer->nextFeature(currentFeature))
      {
        QString featureAttributesString;
         const QgsAttributeMap& map = currentFeature.attributeMap();
         QgsAttributeMap::const_iterator attributeIt = map.constBegin();
         for(; attributeIt != map.constEnd(); ++attributeIt)
         {
            featureAttributesString.append(",");
            featureAttributesString.append(attributeIt.value().toString());
         }
 
 
        currentGeometry = currentFeature.geometry();
        if(!currentGeometry)
        {
            continue;
        }
 
        switch(currentGeometry->wkbType())
        {
            case QGis::WKBPoint:
            case QGis::WKBPoint25D:
                convertPoint(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiPoint:
            case QGis::WKBMultiPoint25D:
                convertMultiPoint(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBLineString:
            case QGis::WKBLineString25D:
                convertLineString(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiLineString:
            case QGis::WKBMultiLineString25D:
                convertMultiLineString(currentGeometry, featureAttributesString \
		theTextStream);
                break;
 
            case QGis::WKBPolygon:
            case QGis::WKBPolygon25D:
                convertPolygon(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
 
            case QGis::WKBMultiPolygon:
            case QGis::WKBMultiPolygon25D:
                convertMultiPolygon(currentGeometry, featureAttributesString, \
		theTextStream);
                break;
        }
      }
    }
}
 
//geometry converter functions
void QgsPointConverterPlugin::convertPoint(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPoint p = geom->asPoint();
    stream << p.x() << "," << p.y();
    stream << attributeString;
    stream << endl;
}
 
void QgsPointConverterPlugin::convertMultiPoint(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPoint mp = geom->asMultiPoint();
    QgsMultiPoint::const_iterator it = mp.constBegin();
    for(; it != mp.constEnd(); ++it)
    {
        stream << (*it).x() << "," << (*it).y();
        stream << attributeString;
        stream << endl;
    }
}
 
void QgsPointConverterPlugin::convertLineString(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPolyline line = geom->asPolyline();
    QgsPolyline::const_iterator it = line.constBegin();
    for(; it != line.constEnd(); ++it)
    {
        stream << (*it).x() << "," << (*it).y();
        stream << attributeString;
        stream << endl;
    }
}
 
void QgsPointConverterPlugin::convertMultiLineString(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPolyline ml = geom->asMultiPolyline();
    QgsMultiPolyline::const_iterator lineIt = ml.constBegin();
    for(; lineIt != ml.constEnd(); ++lineIt)
    {
        QgsPolyline currentPolyline = *lineIt;
        QgsPolyline::const_iterator vertexIt = currentPolyline.constBegin();
        for(; vertexIt != currentPolyline.constEnd(); ++vertexIt)
        {
            stream << (*vertexIt).x() << "," << (*vertexIt).y();
            stream << attributeString;
            stream << endl;
        }
    }
}
 
void QgsPointConverterPlugin::convertPolygon(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsPolygon polygon = geom->asPolygon();
    QgsPolygon::const_iterator it = polygon.constBegin();
    for(; it != polygon.constEnd(); ++it)
    {
        QgsPolyline currentRing = *it;
        QgsPolyline::const_iterator vertexIt = currentRing.constBegin();
        for(; vertexIt != currentRing.constEnd(); ++vertexIt)
        {
            stream << (*vertexIt).x() << "," << (*vertexIt).y();
            stream << attributeString;
            stream << endl;
        }
    }
}
 
void QgsPointConverterPlugin::convertMultiPolygon(QgsGeometry* geom, const QString& \
attributeString, QTextStream& stream) const
{
    QgsMultiPolygon mp = geom->asMultiPolygon();
    QgsMultiPolygon::const_iterator polyIt = mp.constBegin();
    for(; polyIt != mp.constEnd(); ++polyIt)
    {
        QgsPolygon currentPolygon = *polyIt;
        QgsPolygon::const_iterator ringIt = currentPolygon.constBegin();
        for(; ringIt != currentPolygon.constEnd(); ++ringIt)
        {
            QgsPolyline currentPolyline = *ringIt;
            QgsPolyline::const_iterator vertexIt = currentPolyline.constBegin();
            for(; vertexIt != currentPolyline.constEnd(); ++vertexIt)
            {
                stream << (*vertexIt).x() << "," << (*vertexIt).y();
                stream << attributeString;
                stream << endl;
            }
        }
    }
}
 
QGISEXTERN QgisPlugin* classFactory(QgisInterface* iface)
{
  return new QgsPointConverterPlugin(iface);
}
 
QGISEXTERN QString name()
{
  return "point converter plugin";
}
 
QGISEXTERN QString description()
{
  return "A plugin that converts vector layers to delimited text point files";
}
 
QGISEXTERN QString version()
{
  return "0.00001";
}
 
// Return the type (either UI or MapLayer plugin)
QGISEXTERN int type()
{
  return QgisPlugin::UI;
}
 
// Delete ourself
QGISEXTERN void unload(QgisPlugin* theQgsPointConverterPluginPointer)
{
  delete theQgsPointConverterPluginPointer;
}
 
\end{verbatim}
 
\subsection{Further information}
 
As you can see, you need information from many different sources to write QGIS C++ 
plugins. Plugin writers need to know C++, the QGIS plugin interface as 
well as Qt4 classes and tools. At the beginning it is best to learn from 
examples and copy the mechanism of existing plugins. 
 
There is a a collection of online documentation that may be usefull for
QGIS C++ programers:
 
\begin{itemize}
\item QGIS Plugin Debugging: \url{http://wiki.qgis.org/qgiswiki/DebuggingPlugins}
\item QGIS API Documentation: \url{http://svn.qgis.org/api_doc/html/}
\item Qt documentation: \url{http://doc.trolltech.com/4.3/index.html}
\end{itemize}