March 30, 2011

JuanF JuanF
Lab Rat
7 posts

How to dynamically add map objects to QML MAP element? Or how to create customised Map widget?

Page  
1

Hi,

I am implementing an application with a rather complicated UI, which has at least 4 views and each view contains widgets like button, slider bar etc. Therefore I want very much to use QML to handle the view and graphics layout management.

However, my application(at least two of views) would need showing a map widget and loading map objects(such as circls, texts, images) dynamically at runtime during application start up. I have browsed some previous threads regarding this issue, and it seems there is difficult to do this with QML Map element at the moment.

I have tested following code in QML:

  1. Map {
  2.             id: map
  3.             plugin : Plugin {
  4.                         name : "nokia"
  5.                     }
  6.  
  7.             anchors.fill: parent
  8.  
  9.             size.width: parent.width
  10.  
  11.             size.height: parent.height
  12.            
  13.             zoomLevel: t_data.zoomLevel
  14.  
  15.             center: Coordinate {latitude: 46.5; longitude: 6.6}
  16.  
  17.             objects: t_data.mapObjectsList
  18.  
  19.  
  20.             onZoomLevelChanged: {
  21.                 console.log("Zoom changed")
  22.             }
  23.  
  24.         } // map

On C++ side:

  1. class TestData : public QObject
  2.  {
  3.      Q_OBJECT
  4.      Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged)
  5.      Q_PROPERTY(QDeclarativeListProperty<QGeoMapObject> mapObjectsList READ mapObjectsList)
  6.  
  7.  public:
  8. ...
  9.      qreal zoomLevel() const{return mZoomLevel;};
  10.      QDeclarativeListProperty<QGeoMapObject> mapObjectsList(){return QDeclarativeListProperty<QGeoMapObject>(this, mls);};
  11.  
  12.  private:
  13.      qreal mZoomLevel;
  14.      QList<QGeoMapObject*> mls;
  15.  
  16. }

And the result is I could set the map zoom level at runtime, but the map objects list is NOT loaded, and no map object was displayed in QML.

I read in earlier threads suggesting to subclass QGeoGraphicsMap and created customised Map widget, then exposed this widget to QML. However, the QGeoGraphicsMap requires QGeoMappingManager instance at the initilizing phase, and I assume that the graphic widget exposed to QML should avoid it, and could be initilized without parameter. Besides I am not very familiar with the Qt graphics widget, should I reimplement some other function like paint()??

Does someone have the experience of a customised map widget subclassing QGeoGraphicsMap which could be used in QML? Can you share some code? :-)

Basically I want to have a map widget and adding map objects to it when application is starting, then QML will use this map widget in different views.

Thank you very much!

Regards,
Juan

EDIT: added @ tags by vcsala

24 replies

March 31, 2011

david.laing david.laing
Lab Rat
65 posts

The QML Map item in the master branch of the git repository has addMapObject(…) and removeMapObject(….) methods.

If you’re stuck with a version of Qt Mobility which doesn’t have those methods then it’s going to be very painful.

My understanding is something like this (although it’s been a while since it was explained to me): You can’t just assign the list of map objects across, so you’ll probably have to maintain your own array of map objects in javascript and then clear and re add the objects to the list of map objects any time that array changes.

You could always write your own QML map item, and that shouldn’t require the subclassing of QGeoGraphicsMap, since you can do it all from QGeoMapData and QGeoMappingManager (which can be initialized by the Plugin item).

Anyhow, if you can use the code from master or if you can wait for the Qt Mobility 1.2 release I’d probably recommend that pretty highly.

 Signature 

David Laing
Location API team
Qt Mobility

March 31, 2011

JuanF JuanF
Lab Rat
7 posts

Hi,

Thank you very much for the reply!

I am using qt mobility 1.1 beta, I wonder if there is version 1.2 for internal download and use?

I have read the documentation of qt mobility 1.2 related to location API part, and I am aware of the addMapObject(MapObject) and removeMapObject(MapObject). But I’m not quite sure whether they are really for adding/removing map objects dynamically…

It seems the parameter used in this two functions is MapObject, which is a QML element. And I understood that instead of putting the MapObject as a child element of Map element, you could have the MapObject outside Map element scope, and add the MapObject to the map at runtime. However, with this approach, one still needs to know how many map objects will be loaded at runtime, and predefine the MapObject element for them in QML. For example, one has to know there will be two map circles created at runtime , then create following qml:

  1. MapCircle {
  2.      id: circle1
  3.      center: coordinate1
  4.      radius: 100
  5.      color: "yellow"
  6.  }
  7. MapCircle {
  8.      id: circle2
  9.      center: coordinate1
  10.      radius: 100
  11.      color: "yellow"
  12.  }
  13.  
  14. ...
  15. onButton2Clicked: {
  16.      map.addMapObject(circle1)
  17.  
  18.      map.addMapObject(circle2)
  19.  
  20.  }

And if you actually have 3 map circles need to be drawn, the addMapObject(MapObject) can not be used to add the circle3.

On the other hand, you could always have the two map circles as map child elements, and set the visibility at runtime, and make it looks like adding/removing object from map.

I am very grateful to be answered by Location API team, and could you clarify a bit more on how to use add/remove map object in QML Map element? I undertood the mobility API version 1.2 will be released pretty soon, and I can waite if it provides features which my application would need.

I prefer using qml map element to creating customised map widget, because I might run into some strange bugs if I use my own widget which I have no clue how to solve it. But I would also like to be clear what qml offers, and use it in a best way.

Regards,
Juan Feng
Contextual Solutions team
Nokia Research Center

March 31, 2011

conny conny
Lab Rat
85 posts

Hi Juan,

since I have been through the same pain I thought I should share my solution with you. It’s probably a hack, but it works for me.

Basically I’ve created a subclass of QGraphicsGeoMap that mostly just wraps the API and exposes some methods to QML that are not available by default.

You are right that we need to have a parameter-less constructor to be able to use that widget within QML. Also you’re right, QGraphicsGeoMap does not have a parameter-less constructor. I’ve solved that by having a static method that will provided the needed parameter. See MapWidget::createManager()

I’ll simply paste my code below. Please note that I’ve not taken the time to make this code work stand-alone. It’s ripped out of one of my projects and I removed as many unnecessary things to not further confuse you. I hope you can get the general idea from that code.

Have a look at addPoi() and removePoi() and createManager(). If you would like to use addPoi() directly from QML, you should change the signature to something like this:

  1.     Q_INVOCABLE void addPoi(double lat, double lon);

In my main.cpp I’m doing the following:

  1.     qmlRegisterType<MapWidget>("mywidgets", 1, 0, "TsMap");

Have fun :)

  1. // map_widget.h
  2. #ifndef MAP_WIDGET_H
  3. #define MAP_WIDGET_H
  4.  
  5. #include <QGraphicsGeoMap>
  6. #include <QGeoMappingManager>
  7. #include <QGeoMapCircleObject>
  8. #include <QGeoMapObject>
  9. #include <QGeoMapPixmapObject>
  10.  
  11. #include "poi_list_model.h"
  12. #include "data_manager.h"
  13.  
  14. QTM_USE_NAMESPACE
  15.  
  16. class MapWidget : public QGraphicsGeoMap
  17. {
  18.     Q_OBJECT
  19.  
  20. public:
  21.     MapWidget();
  22.     ~MapWidget();
  23.  
  24.     void addPoi(PoiData *poi, bool active);
  25.     void removePoi(PoiData *poi);
  26.     void clearPois();
  27.  
  28. signals:
  29.     void poiClicked(QString uuid);
  30.  
  31. protected:
  32.     void mousePressEvent(QGraphicsSceneMouseEvent* event);
  33.     void mouseReleaseEvent(QGraphicsSceneMouseEvent* event);
  34.     void mouseMoveEvent(QGraphicsSceneMouseEvent* event);
  35.  
  36. private:
  37.     static QGeoMappingManager* createManager();
  38. };
  39.  
  40. #endif // MAP_WIDGET_H

March 31, 2011

conny conny
Lab Rat
85 posts

  1. // map_widget.cpp
  2. #include <QDebug>
  3. #include <QGeoServiceProvider>
  4. #include <QGeoMappingManager>
  5. #include <QGeoCoordinate>
  6. #include <QGeoMapCircleObject>
  7. #include <QGeoMapPixmapObject>
  8. #include <QGeoMapGroupObject>
  9. #include <QGraphicsSceneMouseEvent>
  10. #include <QGeoBoundingBox>
  11. #include <QtCore>
  12.  
  13. #include "map_widget.h"
  14.  
  15. // A widget for QML, therefore we need the parameter-less constructor.
  16. MapWidget::MapWidget() :
  17.     QGraphicsGeoMap(createManager())
  18. {
  19.     setCenter(QGeoCoordinate(51.05, 13.73));
  20.     setZoomLevel(17);
  21. }
  22.  
  23. MapWidget::~MapWidget()
  24. {
  25. }
  26.  
  27. // This method is a bit a heck. We actually call it before the object
  28. // is completely created. We need to do this, because our parent class
  29. // needs a QGeoMappingManager passed to its constructor.
  30. QGeoMappingManager* MapWidget::createManager()
  31. {
  32.     qDebug() << "INFO: Creating mapping manager";
  33.     // We have to access this as static member, because we can't get anything
  34.     // inside this object from the outside. The reason is, that this method
  35.     // is called by the constructor and that we cannot add values to the
  36.     // constructor, because QML always needs a parameter-less constructor.
  37.     QGeoServiceProvider *serviceProvider = new QGeoServiceProvider("osm", params);
  38.     QGeoMappingManager *mappingManager = serviceProvider->mappingManager();
  39.     if (mappingManager == 0) {
  40.         qDebug() << "WARN: Could not load 'osm' plugin. Falling back to 'nokia' plugin.";
  41.         serviceProvider = new QGeoServiceProvider("nokia");
  42.         mappingManager = serviceProvider->mappingManager();
  43.     }
  44.  
  45.     return mappingManager;
  46. }
  47.  
  48. void MapWidget::addPoi(PoiData *poi, bool active)
  49. {
  50.     QGeoCoordinate coord(poi->getLat(), poi->getLon());
  51.  
  52.     QPixmap pixmap;
  53.     if (active) {
  54.         pixmap = QPixmap(":qml/Common/img/poi_active.png");
  55.     } else {
  56.         pixmap = QPixmap(":qml/Common/img/poi_inactive.png");
  57.     }
  58.  
  59.     QGeoMapPixmapObject *pixMapObject = new QGeoMapPixmapObject(coord, QPoint(-26,-65), pixmap);
  60.     pixMapObject->setProperty("uuid", poi->getUuid());
  61.     pixMapObject->setObjectName("poiMarker");
  62.  
  63.     QGeoMapGroupObject *poiMarker = new QGeoMapGroupObject();
  64.     poiMarker->setProperty("uuid", poi->getUuid());
  65.     poiMarker->addChildObject(pixMapObject);
  66.  
  67.     if (active) {
  68.         QGeoMapCircleObject *circle = new QGeoMapCircleObject(coord, poi->getRadius());
  69.         circle->setPen(QPen((poiColor)));
  70.         circle->setBrush(QBrush(poiColor));
  71.         circle->setZValue(-1);
  72.         poiMarker->addChildObject(circle);
  73.     }
  74.  
  75.     addMapObject(poiMarker);
  76. }
  77.  
  78. void MapWidget::removePoi(PoiData *poi)
  79. {
  80.     for (int i = 0; i < poiMarkers.length(); ++i) {
  81.         QGeoMapObject *marker = poiMarkers[i];
  82.         if (marker->property("uuid").toString() == poi->getUuid()) {
  83.             removeMapObject(marker);
  84.             return;
  85.         }
  86.     }
  87. }
  88.  
  89. void MapWidget::clearPois()
  90. {
  91.     for (int i = 0; i < poiMarkers.length(); ++i) {
  92.         QGeoMapObject *obj = poiMarkers[i];
  93.         removeMapObject(obj);
  94.     }
  95. }
  96.  
  97. /*
  98.  * Remember the position. We use this in mouseReleaseEvent
  99.  */
  100. void MapWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
  101. {
  102.     lastPos = event->pos();
  103. }
  104.  
  105. /*
  106.  * If the mouse was moved by a maximum of 30px between press and release
  107.  * we look at the press coordinates and if there is a POI we emit a signal
  108.  * if there are several POIs, we only emit the signal for the first one.
  109.  */
  110. void MapWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
  111. {
  112.     QPointF newPos = event->pos();
  113.     QPointF diff = lastPos - newPos;
  114.  
  115.     if (qAbs(diff.x()) > 30 || qAbs(diff.y()) > 30) {
  116.         return;
  117.     }
  118.  
  119.     QList<QGeoMapObject*> objects = mapObjectsAtScreenPosition(lastPos);
  120.     if (objects.length() > 0) {
  121.         for (int i = 0; i < objects.length(); i++) {
  122.             QGeoMapObject *obj = objects[i];
  123.             if (obj->objectName() == "poiMarker") {
  124.                 QString uuid = obj->property("uuid").toString();
  125.                 emit poiClicked(uuid);
  126.             }
  127.         }
  128.     }
  129. }
  130.  
  131. void MapWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
  132. {
  133.     // Round to int
  134.     QPoint lastPos = event->lastPos().toPoint();
  135.     QPoint pos = event->pos().toPoint();
  136.  
  137.     int dx = lastPos.x() - pos.x();
  138.     int dy = lastPos.y() - pos.y();
  139.  
  140.     pan(dx, dy);
  141.     setFollowPosition(false);
  142. }

March 31, 2011

VCsala VCsala
Lab Rat
339 posts
JuanF: please use @ tags around your code snippets to make it more readable (I have edited your posts in this thread)

April 1, 2011

JuanF JuanF
Lab Rat
7 posts

Hi Conny,

Thank you very much!

Your approach does work! I think for developers who need to use more complicated features than what qml location plugin offers, probably a custimised map widget which extends QGraphicsGeoMap is the best option.

And it is also possible to develope own map widget, which subclasses QGraphicsWidget, by using QGeoMapData and QGeoMappingManager.

Thanks again!

Regards,
Juan

April 1, 2011

conny conny
Lab Rat
85 posts

You’re welcome! I’m glad I could help :)

July 4, 2011

Jano Jano
Lab Rat
19 posts

Hi Conny and Juan,

Would you just post som QML example how do you use tat MapWidget in QML. Im new in QML/Qt wold.

Many Thanks

July 5, 2011

conny conny
Lab Rat
85 posts

Hi Jano,

if you’re new with QML you should start by using the default map item. It has also much improved in QtM 1.2. Have a look here:
http://doc.qt.nokia.com/qtmobility-1.2/declarative-location-mapviewer-mapviewer-qml.html

July 5, 2011

Jano Jano
Lab Rat
19 posts

Hi i tried your example.

Map is succesfully created and pois are shown.. But im not receiving any mousevents.. :(

July 5, 2011

Jano Jano
Lab Rat
19 posts

Actually, im getting mouse events only on device but no in Simulator. Did u have same issue?

Many Thanks!

July 5, 2011

conny conny
Lab Rat
85 posts

I’ve not used the simulator for quite some time and currently still have only an old version installed, so I don’t know. All I know is that you need at least QtM 1.2. If you have this and it’s still not working I’d say it’s a bug and you should report it.

July 6, 2011

Jano Jano
Lab Rat
19 posts

Hi Conny,

I resolve issue. I’m gettin mouse events in simulator aswell.

Do you maybe know if is possible to add mapObject to the map so that onclick event can be handled?
Somehow extend QGeoMapObject?

Unfortunatelly method mapObjectsAtScreenPosition(lastPos) does not work on devices. I’m retreiving empty objects lists.

see bug: http://bugreports.qt.nokia.com/browse/QTMOBILITY-841?page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel

I have latest Qt SDK 1.1.2 and usign Qt Mobility 1.1

Many Thanks for your help

July 6, 2011

conny conny
Lab Rat
85 posts

Yes you’re right. Actually I’ve reported that bug. Unfortunately I don’t know a way to get the mouse events on a devices with QtM < 1.2. I’m also waiting that QtM 1.2 will finally be available for devices.

On Maemo5 I’ve tested it with QtM 1.2 and it’s actually working. On Symbian and for Ovi, we still have to wait :(

July 6, 2011

Jano Jano
Lab Rat
19 posts

Conny,

Thaks again for your help. I resolved the issue.

I’m checking mapObjects boundingBox and check if contains clicked coordinate.

Regards

Page  
1

  ‹‹ [solved] Playing with time      Building with a static map tiles plugin in QML (Qt Mobility) ››

You must log in to post a reply. Not a member yet? Register here!