PDA

View Full Version : creating custom mapping plugin for QtLocation



mateuszzz88
8th September 2011, 11:28
Hello,
I was playing with new QtLocation API for mapping and I liked it. Now I'd like to create my own map, for starters based on OpenStreetMap tiles.

As documentation (http://doc.qt.nokia.com/qtmobility-1.2/location-overview.html#implementing-plugins) instructs, I subclassed QGeoTiledMappingManagerEngine, QGeoTiledMapReply and QGeoServiceProviderFactory. As far as I can see, documentation leaves me in middle of nowhere - those classes are useless alone. Ideally, QGeoServiceProvider::availableServiceProviders(); should return names ("nokia", "myosm"), then I could call serviceProvider = new QGeoServiceProvider("myosm"); but only "nokia" is avilable.

What should I do next?
I expect I should somehow use TEMPLATE = lib, CONFIG += plugin, PLUGIN_TYPE=geoservices (found those in nokia plugin source code), Q_EXPORT_PLUGIN2(mlosm, OsmMapServiceProviderFactory) (for some reason I DIDN'T find this) and maybe some TARTGET/DESTDIR/$$qtLibraryTarget magic, but so far I had no luck.
I have none experience in writing plugins, and all examples I find show how to write some types of plugins (styles, image formats...), but I can't find equivalent type for mapping.
Plese help me.

EDIT:
forgot to mention: Currently I try to work it out on desktop qt and simulator, later I'll add maemo target. I have installed QtSDK, separate desktop Qt-4.7.4 and QtMobility-1.2 for that desktop qt. mapsdemo (from QtMobilityExamples) works flawlessly with "nokia" plugin both on desktop and simulator.

wysota
8th September 2011, 13:19
The classes are not useless alone. They are interfaces you need to implement to create a new data provider. For instance you need to subclass QGeoServiceProviderFactory and reimplement providerName() to return "myosm" if that's what you want your provider to be called. You should also reimplement the remaining methods that will return instances of subclasses of the interfaces provided there that perform the actual tasks.

mateuszzz88
8th September 2011, 13:27
The classes are not useless alone.
By no means I meant they are not needed for the task. It's just that I have subclasses of QGeoServiceProviderFactory and QGeoMappingManagerEngine but to display map I need QGeoMappingManager and I can't find bridge between those two.

wysota
8th September 2011, 13:39
You return the engine that is then used by QGeoMappingManager to perform its tasks. You don't create QGeoMappingManager yourself. You only implement interfaces that are then used by existing classes to fetch required data and do the tasks they are designed to do. It's exactly like with all other kinds of Qt plugins, for instance to provide a plugin for handling image formats you don't return your own "QImageReader" instances but instead you implement QImageIOHandler interface which QImageReader uses to do its job.

mateuszzz88
8th September 2011, 16:19
Ok, I read some more about writing plugins and source code of nokia plugin. So far I think I understand what must be done, but it doesn't work. Here's what I have:

I complied and installed qtmobility with ./configure -prefix /mybin/qtmobility12_custom_forqt474 -qmake-exec /mybin/qt474_custom/bin/qmake (...)

Currently I try to make it work on desktop, so I only use qt from above version, located in /mybin/qt474_custom . I'll worry about other versions when at least this one works.




# OsmPlugin.pro

QT += core gui network

TARGET = $$qtLibraryTarget(qtgeoservices_mlosm)
TEMPLATE = lib
CONFIG += plugin mobility
MOBILITY = location
PLUGIN_TYPE=geoservices

DESTDIR = $$[QT_INSTALL_PLUGINS]/geoservices

SOURCES += \
osmtilereply.cpp \
osmmapserviceproviderfactory.cpp \
osmmappingmanagerengine.cpp

HEADERS += \
osmtilereply.h \
osmmapserviceproviderfactory.h \
osmmappingmanagerengine.h

# To use my suplied version of qtmobility for desktop
INCLUDEPATH +=/mybin/qtmobility12_custom_forqt474/include/QtLocation \
/mybin/qtmobility12_custom_forqt474/include/QtMobility
LIBS += -L/mybin/qtmobility12_custom_forqt474/lib -lQtLocation

symbian {
# Load predefined include paths (e.g. QT_PLUGINS_BASE_DIR) to be used in the pro-files
load(data_caging_paths)
MMP_RULES += EXPORTUNFROZEN
TARGET.UID3 = 0xE7FBA200
TARGET.CAPABILITY =
TARGET.EPOCALLOWDLLDATA = 1
pluginDeploy.sources = OsmPlugin.dll
pluginDeploy.path = $$QT_PLUGINS_BASE_DIR/OsmPlugin
DEPLOYMENT += pluginDeploy
}

unix:!symbian {
maemo5 {
target.path = /opt/usr/lib
} else {
target.path = /usr/lib
}
INSTALLS += target
}

OTHER_FILES += \
qtc_packaging/debian_fremantle/rules \
qtc_packaging/debian_fremantle/README \
qtc_packaging/debian_fremantle/copyright \
qtc_packaging/debian_fremantle/control \
qtc_packaging/debian_fremantle/compat \
qtc_packaging/debian_fremantle/changelog





#header for my plugin class

#ifndef OSMMAPSERVICEPROVIDERFACTORY_H
#define OSMMAPSERVICEPROVIDERFACTORY_H

#include <QGeoServiceProviderFactory>
#include "osmmappingmanagerengine.h"
using namespace QtMobility;

class OsmMapServiceProviderFactory : public QObject, public QGeoServiceProviderFactory
{
//QObject is needed for Q_EXPORT_PLUGIN2 - it works only with QObject derived classes,
//and QGeoServiceProviderFactory is not one of them.
Q_OBJECT
public:
explicit OsmMapServiceProviderFactory();
QString providerName() const {return "mlosm";}
int providerVersion() const {return 1; }
QGeoMappingManagerEngine* createMappingManagerEngine(
const QMap<QString, QVariant> & parameters,
QGeoServiceProvider::Error * error,
QString * errorString ) const;

};

#endif // OSMMAPSERVICEPROVIDERFACTORY_H



#implementation for my plugin class

#include "osmmapserviceproviderfactory.h"

OsmMapServiceProviderFactory::OsmMapServiceProvide rFactory()
{}

QGeoMappingManagerEngine *
OsmMapServiceProviderFactory::createMappingManager Engine(
const QMap<QString, QVariant> &parameters,
QGeoServiceProvider::Error *error,
QString *errorString) const
{
return new OsmMappingManagerEngine(parameters,0);
}

Q_EXPORT_PLUGIN2(qtgeoservices_mlosm, OsmMapServiceProviderFactory)


When building a project, the file /mybin/qt474_custom/plugins/geoservices/libqtgeoservices_mlosm.so is created, as expected. To be on save side, I copy it to /mybin/qtmobility12_custom_forqt474/plugins/geoservices/ where libqtgeoservices_nokia.so (default nokia plugin) was put after I compiled and installed qtmobility.

In different project

QApplication a(argc, argv);
qDebug()<<a.libraryPaths(); shows:
("/mybin/qt474_custom/plugins", "/home/pro/tests/mapsdemo-build-desktop-Qt_4_7_4__qt474_custom__Release", "/usr/lib/kde4/plugins")
so the first path contains my plugin file, but

QList<QString> providers = QGeoServiceProvider::availableServiceProviders();
qDebug()<<providers;
shows only ("nokia")
I expected to see ("nokia", "mlosm") and later use
QGeoServiceProvider *serviceProvider2 = new QGeoServiceProvider("mlosm");

What am I doing wrong? As I said earlier, this is my first attempt at writing qt plugin.

wysota
8th September 2011, 16:44
Did you implement all the required methods for the factory class and for the engine class?

mateuszzz88
8th September 2011, 17:02
Did you implement all the required methods for the factory class and for the engine class?
I think so.
For factory I implemented name and version functions and createMappingManagerEngine(...) all those shown above. I believe this is bare minimum, but should be sufficient. Beside that I have subclasses of QGeoTiledMappingManagerEngine and QGeoTiledMapReply:



class OsmMappingManagerEngine : public QGeoTiledMappingManagerEngine
{
Q_OBJECT
QNetworkAccessManager *iNet;
public:
explicit OsmMappingManagerEngine(const QMap<QString, QVariant> & parameters, QObject *parent = 0);
QGeoTiledMapReply* getTileImage(const QGeoTiledMapRequest& request);

};
// implementation:

OsmMappingManagerEngine::OsmMappingManagerEngine(c onst QMap<QString, QVariant> & parameters, QObject *parent) :
QGeoTiledMappingManagerEngine(parameters, parent),
iNet(new QNetworkAccessManager(this))
{
setTileSize(QSize(256,256));
QNetworkDiskCache *cache = new QNetworkDiskCache(this);
cache->setCacheDirectory("cacheDir_tmpdel");
iNet->setCache(cache);
}

QtMobility::QGeoTiledMapReply * OsmMappingManagerEngine::getTileImage(const QtMobility::QGeoTiledMapRequest &request)
{
QString urlSchema = "http://tile.openstreetmap.org/%1/%2/%3.png";
QString urlstr = urlSchema.arg(request.zoomLevel())
.arg(request.column())
.arg(request.row());
QNetworkRequest req(urlstr);
req.setAttribute(QNetworkRequest::CacheLoadControl Attribute, QNetworkRequest::PreferCache);
QNetworkReply *repl = iNet->get(req);
return new OsmTileReply(request, repl);
}


class OsmTileReply : public QGeoTiledMapReply
{
Q_OBJECT
QNetworkReply *iNetReply;
public:
explicit OsmTileReply(const QGeoTiledMapRequest &req, QNetworkReply *netRep);
QByteArray mapImageData () const;
QString mapImageFormat () const {return "png";}
};

//implementation:
OsmTileReply::OsmTileReply(const QGeoTiledMapRequest &req, QNetworkReply *netRep) :
QGeoTiledMapReply(req, netRep),
iNetReply(netRep)
{
connect(iNetReply,SIGNAL(finished()),this,SIGNAL(f inished()));
}

QByteArray OsmTileReply::mapImageData() const
{
QByteArray ret;
if (iNetReply->error()) {
qDebug()<<"OsmTileReply::mapImageData(): error in network reply: "
<<iNetReply->errorString();
} else if (!iNetReply->isFinished()) {
qDebug()<<"OsmTileReply::mapImageData(): reply not yet finished?!";
} else {
ret = iNetReply->readAll();
}
return ret;
}

wysota
8th September 2011, 21:16
Check if any of your methods are ever called (you can use qDebug() for that). Also remove your DESTDIR directive from the project file. Unless you are compiling as root, it will fail. Also make sure your plugin is linked against the same version of mobility as your test application is using (you can do that using ldd).

mateuszzz88
9th September 2011, 00:38
I thought it might be the problem with complicated setup of my dev platform, so I simplified it a little. Now I use almost original QtSDK, only I installed QtMobility libraries on top of QtSdk's desktop qt library. I no longer have separate qt library in /mybin/qt474_custom nor separate mobility install in qtgeoservices_nokia. It is much simpler now and I no longer use additional LIB+= in .pro file. Sample app with nokia map works, so probably I didn't break anything.
Now as for your advices:

Check if any of your methods are ever called (you can use qDebug() for that).
When I start test app, Factory's constructor IS called - it's better than I expected.


Also remove your DESTDIR directive from the project file. Unless you are compiling as root, it will fail.
No, I have whole QtSdk installed in folder that is accessible by normal user, /mybin . I know it's unconventional, but I like short absolute paths and I like to install programs without giving any part of them at any time root privileges. Furthermore, DESTDIR does for me copying the resulting plugin to accesible directory.


Also make sure your plugin is linked against the same version of mobility as your test application is using (you can do that using ldd).
Outputs for ldd for test app and plugin are line by line identical. I mean the paths, values in brackets differ.

I tried to solve problem from other angle. Instead of making the most basic implementation possible from scratch, I copied the whole nokia plugin to devel directory, changed it's name (in Q_EXPORT_PLUGIN2, TARGET and QGeoServiceProviderFactoryNokia::providerName() ) and build it. It compiled and test app was aware of new plugin. Therefore I think the problem is in my source code.

Wysota, thank you very much for your input, it is really appreciated. I am going offline for 3-4 days, but if you have any other advices, I'll try them as soon as I come back. Otherwise I'll try to strip the copied nokia plugin function by function untill it is bare working minimum and build my plugin from there. It'll take time, but should eventually work.
Again: thank you for your time, and if You have any other ideas, please share them.

Added after 20 minutes:

SOLVED!
comparing nokia's and my plugin, I found I was missing Q_INTERFACES(QtMobility::QGeoServiceProviderFactor y) in class definition. It sucks to be newbie... Plugin doesn't work properly, but is visible and loadable.
Thank You Wysota for your input!