QGIS Planet

QGIS Abstract Connections API

 

The goal of the new API is twofold:

  1. provide a unified way to store and retrieve data provider connections in the QGIS settings
  2. provide an abstract set of methods to perform most common operation on DB data sources (e.g. browse tables, drop/create a table/schema, run arbitrary SQL commands etc.)

 

The new API is documented in https://qgis.org/api/classQgsAbstractProviderConnection.html and it provides a few specializations for DB connections (https://qgis.org/api/classQgsAbstractDatabaseProviderConnection.html) and an initial PR implementation for web service-based connections (https://github.com/qgis/QGIS/pull/33045).

 

While the whole of the desired refactoring work was too big for a single grant request, the first work package has been completed and the following data providers have been partially or totally refactored to make use of the new connections API:

  • postgres
  • geopackage (OGR)
  • spatialite

 

The new API was also used to implement the automatic loading of layer dependencies (not part of the grant program).

 

For developers interested in working with the new API, a set Python tests are available to show how to use the methods:  https://github.com/qgis/QGIS/blob/master/tests/src/python/test_qgsproviderconnection_ogr_gpkg.py (see also the postgres and spatialite companion tests).

 

There is still a large amount of work to be done in order to complete all the desired refactoring and to remove all the Python and C++ code that will be ultimately be made redundant. In particular, future work should be undertaken to:

  • port all remaining data providers to the new API
  • refactor and eliminate the remaining DB-manager connectors to make use of the abstract API
  • eliminate duplicate and untested code inside the Processing framework for working with Postgres databases and port the code to the new, stable, well-tested API
  • refactor and eliminate the remaining QGIS browser data items to make use of the abstract API 

 

For further information, the following paragraphs (taken from the original grant proposal) will provide full details about the background of this work.

Background/motivation

  • DB-Manager is an important part of the QGIS interface, which allows browsing/previews of different DB-based data sources, complex queries, management of layers, import-export etc., DB creation and deletion etc.
  • After the QGIS 3.0 release, improvements within the core browser widgets implemented in C++ have resulted in a (constantly growing) degree of overlapping functionality between the browser and db manager.
  • After QGIS 3 API improvements concerning layer import and export functionality, there are many duplicated implementations between browser and db manager – some functionality is better in browser, some functionality is better in db manager. Users are forced to choose between two competing semi-complete alternatives, instead of having one, complete, well integrated solution.
  • There are no unit tests for DB-Manager and this leads to frequent regressions, which (aside from being frustrating for users) consume a substantial part of our development time and budget during the bugfixing programs. Furthermore the nature of large Python codebases like db manager makes it very easy to accidentally break functionality with no warning or errors during development.

 

Proposed solution

We propose to start refactoring the DB-manager plugin functionality into core C++ implementation, reusing existing core API and replacing redundant duplicate functionality.

The clear advantages are:

  • no duplicate functionality, so it’s easier for users to understand and use
  • more usage of well-tested and well-maintained core C++ API
  • testability and immediate feedback on API breaks (an advantage of C++ is that the application won’t even build if an API is changed or accidentally misused)
  • better performance
  • the ability to expose database management functionality via stable PyQGIS API, allowing other plugins and scripts to utilise this functionality. In future, Processing algorithms may also be developed which would take advantage of these functions (e.g. “create schema”, “drop table”, “vacuum table” algorithms)
  • DB management functionality would be available within the main QGIS window (from the Browser panel), instead of as a separate dialog.

 

Grant proposal package

The above mentioned work is too large to be completed within a single grant, so what we propose here is to start the refactoring needed in order to have a core stable C++ API that can be used by the application and the plugins and that will be available to fully move DB manager to C++ API in the future avoiding duplication of code and functionality.

  • create an interface for databases that expose the required functions to a coherent API
  • add missing tests and documentation for the a.m. API
  • porting some basic functions from db manager to the new api:
    • create table (with native field types support)
    • create schema
    • delete table
    • Rename table

The API will be exposed through the browser and it will be used by the DB manager instead of the Python implementation that is currently used.

The post QGIS Abstract Connections API first appeared on Open Web Solutions, GIS & Python Development.

OpenCL acceleration now available in QGIS

What is OpenCL?

From https://en.wikipedia.org/wiki/OpenCL:

OpenCL (Open Computing Language) is a framework for writing programs that execute across heterogeneous platforms consisting of central processing units (CPUs), graphics processing units (GPUs), digital signal processors (DSPs), field-programmable gate arrays (FPGAs) and other processors or hardware accelerators. OpenCL specifies programming languages (based on C99 and C++11) for programming these devices and application programming interfaces (APIs) to control the platform and execute programs on the compute devices. OpenCL provides a standard interface for parallel computing using task- and data-based parallelism.

Basically, you write a program and you execute it on a GPU (or, less frequently, on a CPU or on a DSP) taking advantage of the huge parallel programming capabilities of the modern graphic cards.

Depending on many different factors, the speed gain can vary to a great extent, but it is typically around one order of magnitude.

How QGIS benefits from OpenCL?

The work I’ve done consisted in integrating OpenCL support into QGIS and writing all the utilities to load, build and run OpenCL programs.

For now, I’ve ported the following QGIS core algorithms, all of them are availabe in processing:

  • slope
  • aspect
  • hillshade
  • ruggedness

Since the framework to support OpenCL is now in place, I think that more algorithms will be ported over the time.

During this development, even if was not in scope, the hillshade renderer has been optimized for speed and it can also benefit of OpenCL acceleration.

How to activate OpenCL support

OpenCL support is optional and opt-in, to use it, you need to activate it into the QGIS options dialog like shown in the screenshot below:

How much performance gain can I expect?

Well, YMMV, but here are some figures for a big DEM raster, low values mean faster execution.

GDAL means CPU execution using the GDAL processing algorithm.

How to install the OpenCL drivers?

Of course it depends on your specific hardware and on your O.S., AMD, NVidia and Intel have different distributions channels, in general the driver for your graphic card will also provide the OpenCL driver, if your GPU is compatible, if OpenCL is not available on your current machine, try to Google for OpenCL, your O.S. and graphic card.

If there is no OpenCL support for your graphic card, you might try to install a driver for your GPU (Intel for example provides them) and you will probably have a decent acceleration even if not as much as you can get on a real graphic card.

This fact worths some more explanation: you might ask your self why running and algorithm directly on the CPU and running it on the same CPU but using OpenCL would make any difference and the reason why it is generally faster by using OpenCL is that OpenCL will run the algorithm in parallel on all cores of your CPU, while a normal application (and QGIS does not make an exception here) will use a single core.

How to build QGIS with OpenCL support on Ubuntu

Just a quick note: you’ll need to install the OpenCL headers and the ICD library:

sudo apt-get install opencl-headers ocl-icd-opencl-dev

 

Credits

I started this work as a proof of concept in my spare time (that it is not much, lately) and when I realized that it was promising, I submitted a QGIS grant proposal in order to allocate some working time to port more algorithms, write tests and polish the implementation.

This work would not be possible without all the generous sponsors and donors that feed the QGIS grant program year after year, many thanks to the QGIS community for this amazing support!

Jürgen Fischer was as usual very helpful and took care of the windows builds, now available in OSGeo4W packages.

Nyall Dawson helped with the code review and with testing the implementation on different cards and machines.

Matthias Kuhn reviewed the code.

Even Rouault pointed me to some highly efficient GDAL algorithm optimizations that I’ve been able to integrate in QGIS.

 

 

The post OpenCL acceleration now available in QGIS first appeared on Open Web Solutions, GIS & Python Development.

Create a QGIS vector data provider in Python is now possible

 

Why python data providers?

My main reasons for having Python data provider were:

  • quick prototyping
  • web services
  • why not?

 

This topic has been floating in my head for a while since I decided to give it a second look and I finally implemented it and merged for the next 3.2 release.

 

How it’s been done

To make this possible I had to:

  • create a public API for registering the providers
  • create the Python bindings (the hard part)
  • create a sample Python vector data provider (the boring part)
  • make all the tests pass

 

First, let me say that it wasn’t like a walk in the park: the Python bindings part is always like diving into woodoo and black magic recipes before I can get it to work properly.

For the Python provider sample implementation I decided to re-implement the memory (aka: scratch layers) provider because that’s one of the simplest providers and it does not depend on any external storage or backend.

 

How to and examples

For now, the main source of information is the API and the tests:

To register your own provider (PyProvider in the snippet below) these are the basic steps:

metadata = QgsProviderMetadata(PyProvider.providerKey(), PyProvider.description(), PyProvider.createProvider)
QgsProviderRegistry.instance().registerProvider(metadata)

To create your own provider you will need at least the following components:

  • the provider class itself (subclass of QgsVectorDataProvider)
  • a feature source (subclass of QgsAbstractFeatureSource)
  • a feature iterator (subclass of QgsAbstractFeatureIterator)

Be aware that the implementation of a data provider is not easy and you will need to write a lot of code, but at least you could get some inspiration from the existing example.

 

Enjoy wirting data providers in Python and please let me know if you’ve fond this implementation useful!

The post Create a QGIS vector data provider in Python is now possible first appeared on Open Web Solutions, GIS & Python Development.

QGIS 3 Server deployment showcase with Python superpowers

Recently I was invited by the colleagues from OpenGIS.ch to lend a hand in a training session about QGIS server.

This was a good opportunity to update my presentation for QGIS3, to fix a few bugs and to explore the powerful capabilities of QGIS server and Python.

As a result, I published the full recipe of a Vagrant VM on github: https://github.com/elpaso/qgis3-server-vagrant

The presentation is online here: http://www.itopen.it/bulk/qgis3-server/

What’s worth mentioning is the sample plugins (I’ll eventually package and upload them to the official plugin site):

 

The VM uses 4 different (although similar) deployment strategies:

  • good old Apache + mod_fcgi and plain CGI
  • Nginx + Fast CGI
  • Nginx + standalone HTTP Python wrapped server
  • Nginx + standalone WSGI Python wrapped server

Have fun with QGIS server: it was completely refactored in QGIS 3 and it’s now better than ever!

 

The post QGIS 3 Server deployment showcase with Python superpowers first appeared on Open Web Solutions, GIS & Python Development.

Use your android phone’s GPS in QGIS

Do you want to share your GPS data from your phone to QGIS? Here is how:   QGIS comes with a core plugin named GPS Tools that can be enabled in the Plugin installer dialog:   There are several ways to forward data from your phone and most of them are very well described in the QGIS manual page: https://docs.qgis.org/testing/en/docs/user_manual/working_with_gps/plugins_gps.html What I’m going to describe here is mostly useful when your phone and your host machine running QGIS are on the same network (for example they are connected to the same WiFi access point) and it is based on the simple application GPS 2 NET   Once the application is installed and started on your phone, you need to know the IP address of the phone, on a linux box you can simply run a port scanner and it will find all devices connected to the port 6000 (the default port used by GPS 2 NET):  

# Assuming your subnet is 192.168.9

nmap -p 6000 192.168.1.*

Nmap scan report for android-8899989888d02271.homenet.telecomitalia.it (192.168.99.50)
Host is up (0.0093s latency).
PORT STATE SERVICE
6000/tcp open X11

  Now, in QGIS you can open the plugin dialog through Vector -> GPS -> GPS Tools and enter the IP address and port of your GPS device:   Click on Connect button on the top right corner (mouse over the gray square for GPS status information)   Start digitizing!

The post Use your android phone’s GPS in QGIS first appeared on Open Web Solutions, GIS & Python Development.

Welcome QGIS 3 and bye bye Madeira

Last week I’ve been in Madeira at the hackfest, like all the past events this has been an amazing happening, for those of you who have never been there, a QGIS hackfest is typically an event where QGIS developers and other pasionate contributors like documentation writers, translators etc. gather together to discuss the future of their beloved QGIS software. QGIS hackfest are informal events where meetings are scheduled freely and any topic relevant to the project can be discussed. This time we have brought to the table some interesting topics like:

  • the future of processing providers: should they be part of QGIS code or handled independently as plugins?
  • the road forward to a better bug reporting system and CI platform: move to gitlab?
  • the certification program for QGIS training courses: how (and how much) training companies should give back to the project?
  • SWOT analysis of current QGIS project: very interesting discussion about the status of the project.
  • QGIS Qt Quick modules for mobile QGIS app
Tehre were also some mentoring sessions where I presented:
  • How to set up a development environment and make your first pull request
  • How to write tests for QGIS (in both python and C++)
  At this link you can find all the video recordings of the sessions: https://github.com/qgis/QGIS/wiki/DeveloperMeetingMadeira2018   Here is a link to the Vagrant QGIS developer VM I’ve prepared for the session: https://github.com/elpaso/qgis-dev-vagrant/   I’ve got a good feedback from other devs about my sessions and I’m really happy that somebody found them useful, one of the main goals of a QGIS hackfest should really be to help other developers to ramp up quicly into the project. Other than that, I’ve also find the time to update to QGIS 3.0 some of my old plugins like GeoCoding and QuickWKT.   Thanks to Giovanni Manghi and to Madeira Government for the organizazion and thanks to all QGIS sponsors and donors!   About me: I started as a QGIS plugin author, continued as the developer of the plugin official repository at https://plugins.qgis.org and now I’m one of the top 5 QGIS core contributors. After almost 10 years that I’m in the QGIS project I’m now not only a proud member of the QGIS community but also an advocate for the open source GIS software movement.

The post Welcome QGIS 3 and bye bye Madeira first appeared on Open Web Solutions, GIS & Python Development.

Building QGIS master with Qt 5.9.3 debug build

Building QGIS from sources is not hard at all on a recent linux box, but what about if you wanted to be able to step-debug into Qt core or if you wanted to build QGIS agains the latest Qt release? Here things become tricky. This short post is about my experiments to build Qt and and other Qt-based dependencies for QGIS in order to get a complete debugger-friendly build of QGIS.   Start with downloading the latest Qt installer from Qt official website: https://www.qt.io/download-qt-for-application-development choose the Open Source version.   Now install the Qt version you want to build, make sure you check the Sources and the components you might need. Whe you are done with that, you’ll have your sources in a location like /home/user/Qt/5.9.3/Src/ To build the sources, you can change into that directory and issue the following command – I assume that you have already installed all the dependencies normally needed to build C++ Qt programs – I’m using clang here but feel free to choose gcc, we are going to install the new Qt build into /opt/qt593.

./configure -prefix /opt/qt593 -debug -opensource -confirm-license -ccache -platform linux-clang
When done, you can build it with
make -j9
sudo make install
  To build QGIS you also need three additional Qt packages   QtWebKit from https://github.com/qt/qtwebkit (you can just download the zip): Extract it somewhere and build it with
/opt/qt593/bin/qmake WebKit.pro
make -j9
sudo make install
  Same with QScintila2 from https://www.riverbankcomputing.com/software/qscintilla
/opt/qt593/bin/qmake qscintilla.pro
make -j9
sudo make install
  QWT is also needed and it can be downloaded from https://sourceforge.net/projects/qwt/files/qwt/6.1.3/ but it requires a small edit in qwtconfig.pri before you can build it: set QWT_INSTALL_PREFIX = /opt/qt593_libs/qwt-6.1.3 to install it in a different folder than the default one (that would possibly overwrite a system install of QWT). The build it with:
/opt/qt593/bin/qmake qwt.pro
make -j9
sudo make install
  If everything went fine, you can now configure Qt Creator to use this new debug build of Qt: start with creating a new kit (you can probably clone a working Qt5 kit if you have one). What you need to change is the Qt version (the path to cmake) to point to your brand new Qt build,: Pick up a name and choose the Qt version, but before doing that you need to click on Manage… to create a new one: Now you should be able to build QGIS using your new Qt build, just make sure you disable the bindings in the CMake configuration: unfortunately you’d also need to build PyQt in order to create the bindings.   Whe QGIS is built using this debug-enabled Qt, you will be able to step-debug into Qt core libraries! Happy debugging!  

The post Building QGIS master with Qt 5.9.3 debug build first appeared on Open Web Solutions, GIS & Python Development.

Essen 2017 QGIS Hackfest

Another great QGIS hackfest is gone, and it’s time for a quick report. The location was the Linux Hotel, one of the best places where open source developers could meet, friendly, geek-oriented and when the weather is good, like this time, villa Vogelsang is a wonderful place to have a beer in the garden while talking about software development or life in general. This is a short list of what kept me busy during the hackfest:

  • fixed some bugs and feature requests on the official QGIS plugin repo that I’m maintaining since the very beginning
  • make the QGIS official plugin repository website mobile-friendly
  • QGIS Server Python Plugin API refactoring, I’ve completed the work on the new API, thanks to the ongoing server refactoring it’s now much cleaner than it was in the first version
  • attribute table bugs: I started to address some nasty bugs in the attribute table, some of those were fixed during the week right after the hackfest
  • unified add layer button, we had a productive meeting where we decided the path forward to implement this feature, thanks to Boundless that is funding the development, this feature is what’s I’m currently working on these days
Thanks to all QGIS donors and funders that made yet another great hackfest possible and in particular to Boundless Spatial Inc. for funding my personal expenses.    

The post Essen 2017 QGIS Hackfest first appeared on Open Web Solutions, GIS & Python Development.

QGIS Developer Sprint in Lyon

QGIS Developer Sprint in Lyon   QGIS Server 3.0 is going to be better than ever! Last week I attended to the mini code-sprint organized by the french QGIS developers in Lyon.   The code sprint was focused on QGIS Server refactoring to reach the following goals:

  • increase maintainability through modularity and clean code responsibilities
  • increase performances
  • better multi-project handling and caching
  • scalability
  • multi threaded rendering
By working for different companies on such a big Open Source project like QGIS, coordination between developers is fundamentally achieved through those kind of events. We were a small group of engaged QGIS Server developers and I think that the alternance between brainstorming and coding has proven to be very productive: after two days we were able to set common milestones and commitments that will ensure a bright future to QGIS Server. A huge and warm thank to the french QGIS developers that organized this meeting!   Photo: courtesy of Règis Haubourg    

The post QGIS Developer Sprint in Lyon first appeared on Open Web Solutions, GIS & Python Development.

A new QGIS plugin allows dynamic filtering of values in forms

   

This plugin has been partially funded (50%) by ARPA Piemonte.

Description

This is a core-enhancement QGIS plugin that makes the implementation of complex dynamic filters in QGIS attribute forms an easy task. For example, this widget can be used to implement drill-down forms, where the values available in one field depend on the values of other fields.

Download

The plugin is available on the official QGIS Python Plugin Repository and the source code is on GitHub QGIS Form Value Relation plugin repository

Implementation

The new “Form Value Relation” widget is essentially a clone of the core “Value Relation” widget with some important differences: When the widget is created:
  • the whole unfiltered features of the related layer are loaded and cached
  • the form values of all the attributes are added to the context (see below)
  • the filtering against the expression happens every time the widget is refreshed
  • a signal is bound to the form changes and if the changed field is present in the filter expression, the features are filtered against the expression and the widget is refreshed

Using form values in the expression

A new expression function is available (in the “Custom” section):
CurrentFormValue('FIELD_NAME')
This function returns the current value of a field in the editor form.

Note

  1. This function can only be used inside forms and it’s particularly useful when used together with the custom widget `Form Value Relation`
  2. If the field does not exists the function returns an empty string.

Visual guide

  Download the example project.   This is the new widget in action: changing the field FK_PROV, the ISTAT values are filtered according to the filter expression.
The new widget in action

The new widget drill-down in action

layer_config_fields

Choosing the new widget

Configuring the widget

Configuring the widget

Configuring the expression

Configuring the expression to read FK_PROV value from the form

News from QGIS HackFest in Las Palmas

First I wish to thank Pablo & friends for the amazing organization, unfortunately I couldn’t spend more than two full days there, but those two days have been memorable! Here is a picture of one of the most interesting discussions (photo: courtesy of Pablo). QGIS discussion at the developer meeting in Las Palmas An hack fest is an event for writing good code but what it’s really good at is to establish and cultivate relations with other coders, to exchange opinions and ideas and last but not least to have some fun and make new friends.   This time, we have had many interesting presentations and a couple of meetings where we spoke about technical aspects of the project management and infrastructure and about some important challenges, both in terms of code size and economical implications for who relies on it, that a growing project must face.   The latter was something I’ve also been considering for a while: now that pull requests (PR) for new features are coming down the pipeline, we must find a better way to manage their queue by giving a clear and transparent approval path and deadline. This management and approval process cannot rely entirely on volunteer work, the main reason being that most of the times the PR proposers have been paid for that PR and it’s not fair (nor reliable) that the (sometimes hard) job of doing a code review is not rewarded. On the other end, an investor cannot waste its time and money on a project without having a reasonable good chance to see its work eventually land into the core of QGIS.   Hugo (thanks for that!) organized a meeting to discuss this topic, that crosses personal business interests, ethical considerations and personal beliefs to a point that it’s not really easy to discuss in a calm and objective way, despite the premises, the discussion was very interesting and constructive and a QEP that tries to address at least some of this problems is open for discussion right now: https://github.com/qgis/QGIS-Enhancement-Proposals/issues/52   Another topic we’ve been discussing was how to manage python plugin dependencies, we’ve decided to start by adding a new metadata tag called external_deps that’s supposed to contain the PIP install string for the required packages, since PIP will be a builtin in python 3.4, that will probably solve most of our problems when we’ll integrate that into the plugin manager. At the moment the metadata is not documented nor required, but it’s there to allow for experiments.   We didn’t miss the occasion to talk about the ugly bug that affects fTools, not something I’m going to dig into in this post though.   Of course an hack fest is still a good opportunity for squashing bugs and implement new cool features, I’ve been busy mainly on the following topics:

  1. HiDPI screen support for web view widgets (help and plugin manager/installer)
  2. Form relations editing longstanding bugs
  3. New feature to optionally enter, edit and store Python form init code into the project (and DB), see the picture below
  4. Plugins website maintenance (added new metadata and fixed a few bugs, added an RPC call to export author email for admins)
New QGIS feature to store form init code   Thanks to all participants, to the organizers and to all QGIS sponsors and donors that made this possible!  

QGIS Server powers the new City of Asti WebGIS

A few days ago the new WebGIS of the City of Asti, a 76000 inhabitants city in Piedmont, was launched.  The new WebGIS uses QGIS Server and QGIS Web Client to serve maps and provide street and cadastrial search and location services.

The new WebGIS was developed by ItOpen and is online at: http://sit.comune.asti.it/site/?map=PRGAsti

QGIS Quick WKT plugin iface edition

Some plugin core functions can now be called from a Python console:

g = QgsGeometry.fromWkt('POINT (9.9 43)')
iface.show_geometry(g)
iface.show_geometry(g.buffer(0.2, 2))
iface.show_wkt('POINT (9 45)')
iface.show_wkb(r'0103...') # cut

All functions accept a layer title as optional argument, if None is given, they are automatically added to a Quick WKT GeometryType (memory) layer, such as Quick WKT Polygon for polygons.

QGIS developer meeting in Nødebo

During the hackfest I’ve been working on the refactoring of the server component, aimed to wrap the server into a class and create python bindings for the new classes. This work is now in the PR queue and brings a first working python test for the server itself.

The server can now be invoked directly from python, like in the example below:

 

#!/usr/bin/env python
"""
Super simple QgsServer.
"""

from qgis.server import *
from BaseHTTPServer import *

class handler (BaseHTTPRequestHandler):

    server = QgsServer()

    def _doHeaders(self, response):
        l = response.pop(0)
        while l:
            h = l.split(':')
            self.send_header(h[0], ':'.join(h[1:]))
            self.log_message( "send_header %s - %s" % (h[0], ':'.join(h[1:])))
            l = response.pop(0)
        self.end_headers()

    def do_HEAD(self):
        self.send_response(200)
        response = str(handler.server.handleRequestGetHeaders(self.path[2:])).split('\n')
        self._doHeaders(response)

    def do_GET(self):
        response = str(handler.server.handleRequest(self.path[2:])).split('\n')
        i = 0
        self.send_response(200)
        self._doHeaders(response)
        self.wfile.write(('\n'.join(response[i:])).strip())

    def do_OPTIONS(s):
        handler.do_GET(s)

httpd = HTTPServer( ('', 8000), handler)

while True:
    httpd.handle_request()

The python bindings capture the server output instead of printing it on FCGI stdout and allow to pass the request parameters QUERY_STRING directly to the request handler as a string, this makes writing python tests very easy.

QGIS server python plugins

 

Today it’s a great day for QGIS Server: Python plugins, the project that took me busy during the past two months, has been merged to master and will be available starting with the next QGIS release.

The project has been discussed and approved in Essen during the last QGIS HF (see my presentation about server plugins), thanks to the input and suggestions coming from Marco Hugentobler and Martin Dobias it is now implemented in the more complete and flexible way.

In this article I will introduce the core concepts and the main features of python plugins for QGIS server.

QGIS server plugins architecture

QGIS server provides some core services: WFS, WMS, WCS. What we wanted to achieve was a system to easily add new services and modify existing services through python plugins.

Mi first experiments were limited to a 404 handler that intercepts unhandled requests and hooks into python plugins capturing every stdout output, this was indeed not enough flexible for a full fledged plugins implementation.

The main loop

QGIS server is not different from most web services implementations: it listens for incoming requests, parses the URL query string parameters and returns its output accordingly to the incoming request.

The standard loop before introducing python plugins looked like the following:

  • Get the request
    • create GET/POST/SOAP request handler
    • if SERVICE is WMS/WFS/WCS
      • create WMS/WFS/WCS server passing in request handler
        • call server’s executeRequest()
          • call request handler output method
    • else Exception

Plugins come into play

Server python plugins are loaded once when the FCGI application starts and they should register one or more QgsServerFilter (from this point, you might find useful a quick look to the server plugins API docs). Each filter should implement at least one of three callbacks (aka: hooks):

    1. requestReady
    2. sendResponse
    3. responseComplete

All filters have access to the request/response object (QgsRequestHandler) and can manipulate all its properties (input/output) and can raise exceptions (while in a quite particular way as we’ll see below).

Here is a pseudo code showing how and when the filter’s callbacks are called:

  • Get the request
    • create GET/POST/SOAP request handler
    • pass request to serverIface
    • call plugins requestReady filters
    • if there is not a response
      • if SERVICE is WMS/WFS/WCS
        • create WMS/WFS/WCS server
          • call server’s executeRequest and possibily call sendResponse plugin filters when streaming output or store the byte stream output and content type in the request handler
      • call plugins responseComplete filters
    • call plugins sendResponse filters

    • request handler output the response

requestReady

This is called when the request is ready: incoming URL and data have been parsed and before entering the core services (WMS, WFS etc.) switch, this is the point where you can manipulate the input and perform actions like:

  • authentication/authorization
  • redirects
  • add/remove certain parameters (typenames for example)
  • raise exceptions

You could even substitute a core service completely by changing SERVICE parameter and hence bypassing the core service completely (not that this make much sense though).

Implementation details of server plugins will be discussed in depth in a future article, by now please refer to  QGIS HelloServer plugin for a complete implementation of the examples and methods cited in this article.

 

sendResponse

This is called whenever output is sent to FCGI stdout (and from there, to the client), this is normally done after core services have finished their process and after responseComplete hook was called, but in a few cases XML can become so huge that a streaming XML implementation was needed (WFS GetFeature is one of them), in this case, sendResponse is called multiple times before the response is complete (and before responseComplete is called). The obvious consequence is that sendResponse is normally called once but might be exceptionally called multiple times and in that case (and only in that case) it is also called before responseComplete.

SendResponse is the best place for direct manipulation of core service’s output and while responseComplete is typically also an option, sendResponse is the only viable option  in case of streaming services.

responseComplete

This is called once when core services (if hit) finish their process and the request is ready to be sent to the client. As discussed above, this is  normally called before sendResponse except for streaming services (or other plugin filters) that might have called sendResponse earlier.

responseComplete is the ideal place to provide new services implementation (WPS or custom services) and to perform direct manipulation of the output coming from core services (for example to add a watermark upon a WMS image).

Raising exception from a plugin

Some work has still to be done on this topic: the current implementation can distinguish between handled and unhandled exceptions by setting a QgsRequestHandler property to an instance of QgsMapServiceException, this way the main C++ code can catch handled python exceptions and ignore unhandled exceptions (or better: log them).

This approach basically works but it does not satisfy my pythonic way of handle exceptions: I would rather prefer to raise exceptions from python code to see them bubbling up into C++ loop for being handled there.

Conclusions

The new plugin system is very flexible and allows for basic input/output (i.e. request/response) manipulation and for new services implementation while it remains unobtrusive and has negligible impact on performances, in the next article I will discuss server plugin implementation in depth.

 

See also the second part of this article.

See all QGIS Server related posts

Python SIP C++ bindings tutorial

Since QGIS uses QT libraries, SIP is the natural choice for creating the bindings.

Here are some random notes about this journey into SIP and Python bindings, I hope you’ll find them useful!
We will create a sample C++ library, a simple C++ program to test it and finally, the SIP configuration file and the python module plus a short program to test it.

Create the example library

FIrst we need a C++ library, following  the tutorial on the official SIP website  I created a simple library named hellosip:

 

$ mkdir hellosip
$ cd hellosip
$ touch hellosip.h hellosip.cpp Makefile.lib

This is the content of the header file hellosip.h:

#include <string>

using namespace std;

class HelloSip {
    const string the_word;
public:
    // ctor
    HelloSip(const string w);
    string reverse() const;
};

This is the implementation in file hellosip.cpp , the library just reverse a string, nothing really useful.

#include "hellosip.h"
#include <string>

HelloSip::HelloSip(const string w): the_word(w)
{
}

string HelloSip::reverse() const
{
    string tmp;
    for (string::const_reverse_iterator rit=the_word.rbegin(); rit!=the_word.rend(); ++rit)
        tmp += *rit;
    return tmp;
}

 

Compiling and linking the shared library

Now, its time to compile the library, g++ must be invoked with -fPIC option in order to generate Position Independent Code, -g tells the compiler to generate debug symbols and it is not strictly necessary if you don’t need to debug the library:

g++ -c -g -fPIC hellosip.cpp -o hellosip.o

The linker needs a few options to create a dynamically linked Shared Object (.so) library, first -shared which tells gcc to create a shared library, then the -soname which is the library version name, last -export_dynamic that is also not strictly necessary but can be useful for debugging in case the library is dynamically opened (with dlopen) :

g++ -shared -Wl,-soname,libhellosip.so.1  -g -export-dynamic -o libhellosip.so.1  hellosip.o

At the end of this process, we should have a brand new libhellosip.so.1 sitting in the current directory.

For more informations on shared libraries under linux you can read TLDP chapter on this topic.

 

Using the library with C++

Before starting the binding creation with SIP, we want to test the new library with a simple C++ program stored in a new cpp file: hellosiptest.cpp:

#include "hellosip.h"
#include <string>
using namespace std;
// Prints True if the string is correctly reversed
int main(int argc, char* argv[]) {
  HelloSip hs("ciao");
  cout << ("oaic" == hs.reverse() ? "True" : "False") << endl;
  return 0;
}

To compile the program we use the simple command:

g++ hellosiptest.cpp -g -L.  -lhellosip -o hellosiptest

which fails with the following error:

/usr/bin/ld: cannot find -lhellosip
collect2: error: ld returned 1 exit status

For this tutorial, we are skipping the installation part, that would have created proper links from the base soname, we are doing it now with:

ln -s libhellosip.so.1 libhellosip.so

The compiler should now be happy and produce an hellosiptest executable, that can be tested with:

$ ./hellosiptest
True

If we launch the program we might see a new error:

./hellosiptest: error while loading shared libraries: libhellosip.so.1: cannot open shared object file: No such file or directory

This is due to the fact that we have not installed our test library system-wide and the operating system is not able to locate and dynamically load the library, we can fix it in the current shell by adding the current path to the LD_LIBRARY_PATH environment variable which tells the operating system which directories have to be searched for shared libraries. The following commands will do just that:

export LD_LIBRARY_PATH=`pwd`

Note that this environment variable setting is “temporary” and will be lost when you exit the current shell.

 

 

SIP bindings

Now that we know that the library works we can start with the bindings, SIP needs an interface header file with the instructions to create the bindings, its syntax resembles that of a standard C header file with the addition of a few directives, it contains (among other bits) the name of the module and the classes and methods to export.

The SIP header file hellosip.sip contains two blocks of instructions: the class definition that ends around line 15 and an additional %MappedType block that specifies how the std::string type can be translated from/to Python objects, this block is not normally necessary until you stick standard C types. You will notice that the class definition part is quite similar to the C++ header file hellosip.h:

// Define the SIP wrapper to the hellosip library.

%Module hellosip

class HelloSip {

%TypeHeaderCode
#include <hellosip.h>
%End

public:
    HelloSip(const std::string w);
    std::string reverse() const;
};

// Creates the mapping for std::string
// From: http://www.riverbankcomputing.com/pipermail/pyqt/2009-July/023533.html

%MappedType std::string
{
%TypeHeaderCode
#include 
%End

%ConvertFromTypeCode
    // convert an std::string to a Python (unicode) string
    PyObject* newstring;
    newstring = PyUnicode_DecodeUTF8(sipCpp->c_str(), sipCpp->length(), NULL);
    if(newstring == NULL) {
        PyErr_Clear();
        newstring = PyString_FromString(sipCpp->c_str());
    }
    return newstring;
%End

%ConvertToTypeCode
    // Allow a Python string (or a unicode string) whenever a string is
    // expected.
    // If argument is a Unicode string, just decode it to UTF-8
    // If argument is a Python string, assume it's UTF-8
    if (sipIsErr == NULL)
        return (PyString_Check(sipPy) || PyUnicode_Check(sipPy));
    if (sipPy == Py_None) {
        *sipCppPtr = new std::string;
        return 1;
    }
    if (PyUnicode_Check(sipPy)) {
        PyObject* s = PyUnicode_AsEncodedString(sipPy, "UTF-8", "");
        *sipCppPtr = new std::string(PyString_AS_STRING(s));
        Py_DECREF(s);
        return 1;
    }
    if (PyString_Check(sipPy)) {
        *sipCppPtr = new std::string(PyString_AS_STRING(sipPy));
        return 1;
    }
    return 0;
%End
};

At this point we could have run the sip command by hand but the documentation suggests to use the python module sipconfig that, given a few of configuration variables, automatically creates the Makefile for us, the file is by convention named configure.py:

import os
import sipconfig

basename = "hellosip"

# The name of the SIP build file generated by SIP and used by the build
# system.
build_file = basename + ".sbf"

# Get the SIP configuration information.
config = sipconfig.Configuration()

# Run SIP to generate the code.
os.system(" ".join([config.sip_bin, "-c", ".", "-b", build_file, basename + ".sip"]))

# Create the Makefile.
makefile = sipconfig.SIPModuleMakefile(config, build_file)

# Add the library we are wrapping.  The name doesn't include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_libs = [basename]

# Search libraries in current directory
makefile.extra_lflags= ['-L.']

# Generate the Makefile itself.
makefile.generate()

We now have a Makefile ready to build the bindings, just run make to build the library. If everything goes right you will find a new hellosip.so library which is the python module. To test it, we can use the following simple program (always make sure that LD_LIBRARY_PATH contains the directory where libhellosip.so is found).

import hellosip
print hellosip.HelloSip('ciao').reverse() == 'oaic'

Download

The full source code of this tutorial can be downloaded from this link.

QGIS Web Client GetFeatureInfo formatters

The transformation of a value to an URL address is done automatically in a few cases (this feature is currently undocumented): for example when the column value starts with http or https or a string contained in mediaurl parameter defined in Globaloptions.js.
But if you want more, then you need a real formatting function that given the value (and maybe some more information bits about where the values comes from) returns a properly formatted hyperlink or whatever else you need.

This new feature is currently available in my customformatters branch, and an example formatter is provided in Globaloptions.js and implemented for the helloworld.qgs sample project.

Here is how it works:

// Custom WMS GetFeatureInfo results formatters: you can define custom
// filter functions to apply custom formatting to values coming from
// GetFeatureInfo requests when the user use the "identify" tool.
// The same formatting functions can be normally also used as "renderer"
// function passed to column configuration in the "gridColumns" property
// of the grid configuration of the WMS GetFeatureInfo search panels.

// Example formatter, takes the value, the column name and the layer name,
// normally only the first parameter is used.
function customURLFormatter(attValue, attName, layerName){
    return '<a href="http://www.google.com/search?q=' + encodeURI(attValue) + '" target="_blank">' + attValue + '</a>';
}

// Formatters configuration
var getFeatureInfoCustomFormatters = {
    'Country': { // Layer name
        'name': customURLFormatter // Can be an array if you need multiple formatters
    }
};

If you also want to apply the same formatting to result grids coming from the search panels, you can use the very same functions passing the function in the renderer attribute of the datagrid, as shown around line 18 in the following snippet:

var simpleWmsSearch = {
  title: "Search continent",
  query: 'simpleWmsSearch',
  useWmsRequest: true,
  queryLayer: "Country",
  formItems: [
    {
      xtype: 'textfield',
      name: 'name',
      fieldLabel: "Name",
      allowBlank: false,
      blankText: "Please enter a name (e.g. 'africa')",
      filterOp: "="
    }
  ],
  gridColumns: [
    // Apply the formatter as the "renderer"
    {header: 'Name', dataIndex: 'name', menuDisabled: 'true', renderer: customURLFormatter}
  ],
//  highlightFeature: true,
//  highlightLabel: 'name',
  selectionLayer: 'Country',
  selectionZoom: 0,
  doZoomToExtent: true
};

The result of the formatter applied to both views is in the following picture:

Qgis Web Client Formatters

QGIS development

We started our GIS development activities back in 2001, focusing on free open-source software, we developed and deployed several WebGis websites using GRASS and UNM MapServer plus PHP MapScript.

After a while, we stared our Python migration by using the GeoDjango Framework and a choice of Javascript mapping libraries for the client, in particular, we developed WebGIS front-ends with OpenLayers and GeoEXT.

QGIS python plugins development

For internal usage we developed two popular QGIS plugins:

both plugins are released with GPL license and are available on the official QGIS Python Plugins repository, source code is available on my GitHub account.

Official QGIS Python Plugins Repository

Back in 2010 we started contributing to the QGIS community by starting the implementation of the new official QGIS Python Plugins repository.

The new repository was developed in Python using the Django framework

We are still maintaining the plugins repository and we actively participate at all QGIS HackFests to share ideas and coordinate the development with the rest of the QGIS team.

QGIS Server and QGIS Web Client

For a medium sized public administration we recently developed a complete WebGIS system for cadastrial and planning data. We choose the amazing QGIS Server as mapping engine and QGIS Web Client for the client side.

During the development of the system we contributed to the code of both components by providing patches and bug fixes and by developing a full stack of PHP support services that are now integrated in the core of QGIS Web Client

 

How to read a raster cell with Python QGIS and GDAL

QGIS and GDAL both have Python bindings, you can use both libraries to read a value from a raster cell, since QGIS uses GDAL libraries under the hood, we can expect to read the exact same value with both systems.

 

Here is a short example about how to do it with the two different approaches, we assume that you are working inside the QGIS python console and the project has a raster file loaded, but with just a few modifications, the example can also be run from a standard python console.

The example raster layer is a DTM with 1000 cells width and 2000 cells height, we want to read the value at the cell with coordinates x = 500 and y = 1000.

# First layer in QGIS project is a DTM 2 bands raster
from osgeo import gdal
# You need this to convert raw values readings from GDAL
import struct

# Read the cell with this raster coordinates
x = 500
y = 1000

# Get the map layer registry
reg = QgsMapLayerRegistry.instance()

# Get the first layer (the DTM raster)
qgis_layer = reg.mapLayers().values()[0]

# Open the raster with GDAL
gdal_layer = gdal.Open(rlayer.source())

"""
Fetches the coefficients for transforming between pixel/line (P,L) raster space, 
and projection coordinates (Xp,Yp) space.
    Xp = padfTransform[0] + P*padfTransform[1] + L*padfTransform[2];
    Yp = padfTransform[3] + P*padfTransform[4] + L*padfTransform[5];
In a north up image, padfTransform[1] is the pixel width, and padfTransform[5] 
is the pixel height. The upper left corner of the upper left pixel is 
at position (padfTransform[0],padfTransform[3]).
"""
gt = gldal_layer.GetGeoTransform()

# o:origin, r:rotation, s:size
xo, xs, xr, yo, yr, ys = gt

# Read band 1 at the middle of the raster ( x = 500, y = 1000)
band = gdal_layer.GetRasterBand(1)
gdal_value = struct.unpack('f', band.ReadRaster(x, y, 1, 1, buf_type=band.DataType))[0]

xcoo = xo + xs * x + xr * y
ycoo = yo + yr * x + ys * y

# Read the value with QGIS, we must pass the map coordinates
# and the exact extent = 1 cell size
qgis_value = qgis_layer.dataProvider().identify(QgsPoint(xcoo, ycoo), \
    QgsRaster.IdentifyFormatValue, \
    theExtent=QgsRectangle( xcoo , ycoo, xcoo + xs, ycoo + ys) )\
    .results()[1]

assert(gdal_value == qgis_value)

QGIS and QT: getting ready for HiDPI screens

 

A few months ago I changed my laptop for a Dell M3800 with an amazing 3840×2160 15,6″ display, that means a crazy 282 dpi resolution. I won’t discuss the problems I had to face and resolve to make this thing usable with Kubuntu 14.04 because they are yet some useful resource on the net: https://wiki.archlinux.org/index.php/HiDPI. The executive summary is that KDE and QT/GTK applications work pretty well and without serious usability problems: Firefox, Chrome, Thunderbird have no problems at all if we exclude some really minor glitches like pixelated or too big icons in a few occasions (see the example below).

Small problem with a big icon in Firefox

Small problem with a big icon in Firefox @ 282 dpi

 

QGIS on HiDPI

Unfortunately, QGIS has some serious usability problems on such an high resolution screen and I spent a few hours try to understand how is it possible to fix them and what should be done to avoid them in the first place.

The reason behind is that HiDPI screens usage is increasing and we can expect they’ll gain more and more market share expecially among professional users and GIS users are mostly professional users!

To give you a quick taste of the kind of problems that have to be addressed, take a look at the pictures below.

QGIS statusbar: unreadable on HiDPI

QGIS statusbar: unreadable on HiDPI

qgis-hidpi-pluginmanager

Plugin manager rows are too low and left tab column has a 200 px max width

qgis-hidpi-attributetable-issue

Attribute table rows are too low and icons are too small

 

I tried to fix some of the issues shown above and this is the resulting commit: https://github.com/qgis/QGIS/pull/2014/commits, QGIS run on different platforms and I wasn’t able to test it on all of them, I hope the fixes will not cause problems on platforms and screen sizes other than I could test myself.

But the purpose of this notes it to list some humble tips that could be useful to avoid or fix these kind of problem.

Tip #1: Avoid hardcoded pixel values!

Web developers started the fight with HiDPI screens a few years ago, (buzz)words like responsive design and the move towards mobile platforms forced the developers to consider what a pixel really is in a web context, and the obvious conclusion was that a pixel is not a pixel! Please see the following resources if you’re curious and not familiar with these concepts:

The most obvious and efficient solution for the web browser was to change the concept of pixel from a physical to a logical unit.

Unfortunately, at least in QT the pixel is still a physical pixel, this means the we must avoid to specify the size of GUI elements in pixel units.

 

Qt documentation has a page about Developing Scalable UIs.

This blog page also has some useful tips: HighDPI in KDE Applications.

 

A collection of useful tips for QT developers can be found in the HiDPI KDE wiki page:

  • Do not use QFont setPixelSize but use setPointSize, and better, avoid setting a custom size at all.
  • Try to stick with the possibilities from KGlobalSettings (KGlobalSettings::generalFont(), KGlobalSettings::largeFont() etc.)
  • Do not use a fixed QSize with images (QPixmap, QIcon…)
  • Do not use KIconLoader::StdSizes. Even though it would sound like a good idea to use it, it suffers from the same issue: Hardcoded pixel sizes.
  • To get a user’s configured icon size, include and use the IconSize function (note: this is not a member function): IconSize(KIconLoader::Small). However, be aware that the user might be able to mess up your apps look by setting some insane values here.
  • If you use svg images for icons (for example in plasma) get the standard sizes from some theme elements (e.g. buttons, fonts) and scale your images accordingly. (Don’t do this when using pixel based images)

Some of the tips above are clearly related to KDE, but I’ve found them still useful to get the overall picture.

Coming to QGIS specific problems, there are still many that have to be resolved, but the overall application is somewhat already useable. The issues I have addressed were easy picks and most of the times it was enough to remove the hardcoded values and rely to QT default sizing mechanisms to get a good result, such as in this case (from src/app/qgisapp.cpp):

//mCoordsLabel->setMaximumHeight( 20 );

Things were harder with plugin manager, the MVC delegate that draws the plugins rows contained a lot of hardcoded metrics for icon and text placement, the solution was to change the values to be relative to the configured font height, see all details in my PR to solve plugin manager HiDPI issues

 

This is generally a good idea: we must not make any assumption about the size of the fonts the user will be using.

Tip #2: force resize of dialogs

This is something that you should be aware of if you are using an HiDPI screen to build your GUI.

I first encountered this problem when developing my IPyConsole QGIS python plugin: I designed the GUI with designer-qt4 on my HiDPI screen and forget about testing it on “normal” screens, the result is that the window size of the settings dialog (a relatively small dialog) was set by designer at a crazy value of 1115×710. It was impossible from QT Designer to set a small size by dragging the window corner with the mouse: the application automatically reset the dialog size to the calculated minimum size (which on an HiDPI screen was that huge  1115×710).

The solution in that case was to manually alter the Ui_SettingsDialog.ui file to set a small size (say 300×200) and call adjustSize()

self.settingsDlg.show()
# This will adjust the size according to font/dpi:
self.settingsDlg.adjustSize()
result = self.settingsDlg.exec_()

Tip #3: Icons

I haven’t yet come to a solution for the right size for icons (suggestions welcome!). Of course the first point is to move all icons to SVG, the process has already started but many icons have still to be converted.

But having scalable icons is not enough: we must make sure that the icon size is not hardcoded in the GUI, there are some settings for icon size in QGIS options dialog and that might be the right point to start but we probably need more than one size:

  • size of toolbar icons
  • size of plugin icons
  • size for smaller toolbars (attribute table, python console vertical sidebar to cite a few)

Maybe a 3-sizes approach would be fine (big, medium, small), for an easy configuration, some pre-defined values for HiDPI / 96dpi and HD screens would be useful.

To get an idea of what the attribute table looks like right now on an HiDPI screen, look at the picture below, scaled to 96dpi:

 

qgis-hidpi-attributetable-small-icons-issue96dpi

Icons are too small on an HiDPI screen

 

 

Conclusions

There is still much work to do in order to have an usable interface on HiDPI screens, some issues can be easily solved by avoiding hardcoded sizes and leaving to QT the heavy job of calculating GUI widget sizes.

It’s important that developers (not only core developers but plugin developers too) are aware of this kind of issues and how to avoid them in the first place.

HiDPI screens are just around the corner and it’s better to be prepared.

 

A final note about QT5, it’s not clear to me if and when the move to QT5 will be done and moreover if that move will automatically solve HiDPI issues due to a better HiDPI support, but I’m afraid it won’t.

 

  • Page 1 of 2 ( 24 posts )
  • >>
  • ItOpen
  • qgis

Back to Top

Sustaining Members