Maak een QgsLocator (Plugin) met PyQGIS

Wat is een Locator (plugin)

Een paar maanden geleden, voegde Nyall Dawson stilletjes een klein zoekinvoer(widget) toe aan QGIS: zie de linker benedenhoek:

locatorbel

Mensen die bekend zijn met QtCreator (de Qt-ontwikkelomgeving) zouden het kunnen herkennen als een ‘QtCreator Locator’ clone: een manier om (heel) snel in een project te kunnen zoeken op: woorden, classes, favorieten, help-onderwerken, bestanden enz enz.. Het is eigenlijk een alles kunnen om razendsnel iets op te zoeken OF op te starten. Zie ook de originele Qt documentatie.

Nyall had het briljante idee om zoiets ook toe te voegen aan QGIS… en hij deed dat ook. In de code heet dit een QgsLocator en het laad/registreert zogenaamde ‘QgsLocatorFilters’.
In een nieuwe QGIS kun je daar al direct zoeken op: Acties, Processing Algorithmen, Ruimtelijke Favorieten, Features in de actieve laag, Projectlagen en Project Layouts.
Om even te proberen, tik ‘bel…’ en je ziet dat er wat (buffer) gerelateerde Processing Algoritmen beschikbaar zijn.

Door er dan op te dubbelklikken start je het algorithme, of eigenlijk je start het in processing, supersnel en handig. Maar als je net een kaart van de wereld hebt geladen zoals in bovenstaand plaatje en je klikt daar op ‘Belgium’ dan zoom je naar dat object.

Maar het mooiste nog (vind ik), is dat je als (python) ontwikkelaar heel ‘gemakkelijk’ zelf zo’n QgsLocatorFilter kunt bouwen.

Het eerste wat er bij mij opkwam was: ‘Dit moet je gebruiken om een geocoder aan te roepen…’.

Dus, meteen proberen…

De Locator plugin bouwen

De crux is, om de zogenaamde ‘QgsLocatorFilter’ te implementeren ( Zie de QGIS API documentatie, de PyQGIS Api of de cpp headerfile).

Jouw implementatie van die Class is dan eigenlijk het werkpaard van je Locator Filter. Het zal de invoertekst nemen, hier iets mee doen (in ons geval naar een Online Geocoder sturen), the resultaten verzamelen en dan een rij ‘QgsLocatorResult’-objectjes ervan maken die dan in die zoekrij worden getoond. En het bepaalt wat er wordt gedaan wanneer de gebruiker op een zoekresultaat klikt.

Daarnaast moet je die Class natuurlijk nog even in QGIS lijmen:
– een mini plugin die je dan in de QGIS plugin repo kan zetten, zodat gebruikers je filter kunnen downloaden
– in de plugin ‘registeer’ je je QgsLocatorFilter zodat QGIS die opppikt en toont in de Locator zoekwidget (zie de Nominatim Locator Plugin als je wilt zien hoe dat wordt gedaan)

Sommige geocoders (Google…) hebben tegenwoordig een ‘api key’ nodig om te blijven werken. Daarvoor moet je op het kleine vergrootglas icoontje klikken, zodat er een menu verschijnt en je op ‘Configureren’ kan klikken.

configuremenu

Dat brengt je dan in de ‘Locator’ tab in de Opties Dialoog:

locatoroptions

Je kunt er daar voor kiezen om een Locator Filter altijd te activeren of juist uit te zetten.

Helemaal rechts zie je een (nu niet aktieve) configureerknop. Als jouw Locator ‘True’ retourneert in de code wanneer hasConfigWidget() wordt aangeroepen, dan wordt die knop actief en kun je zelf een (klein) configuratiedialoogje bouwen en tonen.

Een voorbeeldgeocoder: Nominatim_Locator_Plugin

Nominatim (latijn, ‘op naam’) is volgens de OpenStreetMap wiki “a tool to search OSM data by name and address and to generate synthetic addresses of OSM points (reverse geocoding)”. Lees erover in de OSM wiki of de OSM api informatie.

Ik heb nu een ‘Nominatim_Locator_Plugin’ gebouwd die precies dat doet: de QgsLocatorFilter interface implementeren. Je kunt deze zelf via de Plugin Manager van QGIS installeren: zoek op ‘Nominatim’ en installeer de ‘Nominatim Locator Filter’ plugin. Of … bekijk gewoon de Python code.

In die code zie dat dat we in de ‘fetchResults’ wachten tot de gebruiker 2 letters heeft getikt, en dan pas gaan zoeken.
De Nominatim service is nog een speciaal geval: het is gratis, maar het is NIET toegestaan het als een ‘suggest-service’ te gebruiken. Je mag dus niet letter voor letter zoeken, maar alleen op een volledig adres zoeken. Om hieraan te gehoorzamen heb ik toegevoegd dat je zoekterm moet eindigen op een ‘spatie': pas dan wordt het verzoek naar Nominatim gestuurd.

Het supermooie met Nominatim en OpenStreetMap is dat het alle talen kan verwerken. Dus als je in het nederlands
‘vrijheidsbeeld’ intikt, dan krijg je dus gewoon resultaat:

vrijheidsbeeld

Wat technische details
De methoden die je moet implementeren en de beschrijving ervan vind je in de api docs van de qgslocatorfilter.h headerfile.

Een belangrijk ding om je bewust van te zijn is dat het zoeken/ontvangen/tonen van de resultaten allemaal plaatsvindt buiten de ‘applicatie’-thread om. Ik dacht slim te zijn en dus de ‘requests’ naar de services asynchroon af te vuren, maar dat eindigde met een ‘gevecht om threads’. Dus mijn tip: gebruik synchrone HTTP voor je aanroepen.

Dit brengt me op welke Python module je moet gebruiken voor het HTTP-verkeer. In QGIS 2 plugins gebruikte men vaak de Requests-module, of gewoon httplib2 of bouwde hun eigen NetworkAccessManagers.
Het nadeel van het gebruiken van die externe of zelfgemaakte oplossingen is dat QGIS eigen Netwerkinstellingen zoals ‘Proxy Settings’ of ‘Network Timeout’ vaak genegeerd werden…

Voor deze plugin gebruikte ik deze module networkaccessmanager.py.

Het mooie eraan is dat het probeert om een dunne schil te zijn rondom QGIS’ eigen QgsNetworkAccessManager (die op zijn beurt weer een schil is om Qt’s QnetworkAccessManager).
Het gebruikt alle Proxy-instellingen die een gebruiker heeft ingesteld, en het kan ook uitstekend samenwerken met QGIS’ eigen authorisatie-modules en -configuraties, dus het HTTP verkeer wordt geauthoriseerd en geleid volgens de eigen systeeminstellingen.

De Boundless oplossing werkt goed, maar het zou nog mooier zijn als de (beetje kale) QgsNetworkAccessManager C++ implementatie kon worden uitgebreid om ‘m wat Python/gebruiksvriendelijke te maken. Zodat het wat eenvoudiger wordt om redirects, timeouts en speciale headers te behandelen. En zoals in de Boundless module ook kan: kiezen of je de requests synchroon of asynchroon (niet blokkerend) wilt hebben.

Ik heb hiervoor een QEP (QGIS Enhancement Proposal) aangemaakt, ik hoop dat er meer mensen in geinteresseerd zijn.

Toekomst

Een OpenSource project is natuurlijk nooit ‘klaar’, in de positieve zin van ‘nooit af’ :-)

Terwijl je aan het programmeren bent borrelen de ideeen al weer op:

– toch een configuratieschermpje toevoegen aan Nominatim, om bijvoorbeeld te kunnen zoeken op OSM tags (wat dacht je van “alle cafe’s in de de kaart die ik nu voor mijn neus heb”, of nog beter: hier in de buurt :-) )

– de ‘accept-language option’ van Nominatim gebruiken (als in gebruik de huidige ingestelde taal) om zo te kunnen zoeken op: Эйфелева башня of eiffeltoren en dan ook van OSM de resultaten in die taal terug te krijgen.

eiffeltowerrussion

– zoeken met de Google Maps api. Heb je dan wel een ‘api key’ voor nodige dus zul je een configuratieschermpje moeten maken (heb ik al af :-) )

– De PDOK locatieserver gebruiken…. uh… intussen af PDOK Locatieserver Locator Filter:

pdok_nominatim_postcode

– of andere interessante zoekservices gebruiken

Ik hoop dat ik wat interesse heb gewekt, en zie de ‘locators’ wel verschijnen op plugins.qgis.org

Ah, en nog wat tips voor Python QGIS / PyQGIS programmeurs:

Latest Python API docs (thanks Denis): https://qgis.org/pyqgis/master/

https://qgis.org/api/api_break.html (all QGIS2.x – QGIS3.x api breaks and fixes)

One thought on “Maak een QgsLocator (Plugin) met PyQGIS”

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd met *

De volgende HTML tags en attributen zijn toegestaan: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>