Monday, June 7, 2021

PyQGIS for fun - lets write fun little scripts in pyqgis

 There are already many pyqgis scripts that allow you interact with various spatial datasets. In this post, I want to take a look at something different just for fun and hopefully that will help you understanding pygis ecosystem!

So we won't be interacting with core GIS data per se, instead we will write some fun little projects in PyQGIS that has little or nothing to do with GIS data directly.

These project will help us understand pyqgis and the QGIS interface as a whole. Lets get started...


Note: When you are scripting in PyQGIS, you are directly or indirectly using three technologies namely: Qt, PyQt and PyQGIS

Qt is a widget toolkit for creating Graphical User Interfaces (GUI) in C++.

PyQt is the Python interface to Qt. PyQt provides classes and functions to interact with Qt widgets.

PyQGIS uses PyQt instead of the default python GUI library (TkInter) that comes with most python installations. TkInter was removed from the python that comes with QGIS, the last time I check on QGIS version 3.16 it throughs error like so...


I think the QGIS developers don't want you to use another GUI tool kit within QGIS (which I think they are right, as doing this will only introduce another layer of complexity), sorry TkInter lovers 😊.


Project 1: Customizing QGIS interface Color

In this project, lets change the color of the QGIS interface. By default, QGIS interface is a mixture of pure WhiteSmoke and LightGrey colors. Let customize the colors as per our color of choice.


Feel free to use the color of your choice. As for me, I love Pinkish (somewhat pink color) just like "The Pink Panther". These two lines of code will do the magic.
app = QApplication.instance()
app.setStyleSheet("QWidget {color: blue; background-color: pink;}")


You can take this further by making the pinkish color change over a range of pink colors like this ['#FFBCD8', '#FF8CC8', '#FF7CB8', '#FF6CA8', '#FA5C98', '#EA4C88'] at certain time interval.

from PyQt5 import QtTest

pinkish = ['#FFBCD8', '#FF8CC8', '#FF7CB8', '#FF6CA8', '#FA5C98', '#EA4C88']

for _ in range(5000):   
    for pink in pinkish:
        app = QApplication.instance()
        app.setStyleSheet("QWidget {color: blue; background-color: " + pink + ";}")
        
        QtTest.QTest.qWait(2000)

print('Pinkish is finished :)')



Project 2: Random "Chuck Norris" Jokes on QGIS title bar

Here, I will use the free "Chuck Norris Jokes API" to grab random jokes made by Chuck Norris and display it on my QGIS title bar.


import requests

try:
    # Get random jokes from the API...
    jokes_url = 'https://api.chucknorris.io/jokes/random'
    joke = requests.get(jokes_url, timeout=10).json()

    # joke.keys()
    # Get the actual joke text from value key...
    joke_txt = joke['value']
    print(joke_txt)

    # Write joke to main window title bar...
    joke_txt = f'*** {joke_txt} ***'
    iface.mainWindow().setWindowTitle(joke_txt)
    
except Exception:
    # Write joke to main window title bar...
    joke_txt = f"*** ERROR: NO CHUCK NORRIS'S JOKE AVAILABLE ***"
    iface.mainWindow().setWindowTitle(joke_txt)


We can extend this script to continuously display random jokes forever at certain time interval.



Project 3: Love Icon on tools bar

Here we will add a custom icon on the tools bar that will display a love message each time it is clicked. For example, in my case, I will add a map of Africa icon that displays the text "I love Africa" each time I click on it. You can use the same concept for your wife or girl friend if you prefer to do so.

The icon will appear on the 'Plugins Toolbar', so if you don't have it enabled on you tools bar, just right click on empty space and select 'Plugins Toolbar' to enable it.


With the code below, the text *** I Love Africa *** should be printed on the interactive python shell whenever the button is pressed as seen below.

# Construct path to icon svg file...
icon = 'Africa.svg'
data_dir = os.path.join(os.path.expanduser('~'), 'Desktop')
icon_path = os.path.join(data_dir, icon)

def africa_func():
    print('*** I Love Africa ***')

# Create QAction obj and connect trigger function...
action = QAction('African Love')
action.triggered.connect(africa_func)

action.setIcon(QIcon(icon_path)) # Set icon to action obj
iface.addToolBarIcon(action) # Add icon to toolbar

Instead just printing out our text, lets take this even further by displaying a windows with our text (QLabel text) on it like this...



from qgis import PyQt
from qgis.PyQt import QtWidgets

class AfricaWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("I love Africa...")
        self.setFixedWidth(550)
        self.setFixedHeight(150)
        # Create layout obj...
        layout = QVBoxLayout()
        
        # Create label widget...
        nameLabel = QLabel(self)
        nameLabel.setText("<font color=blue>I Love Africa...</font>")
        
        # setting font and size...
        nameLabel.setFont(QFont('Arial', 60))
        
        # Add widget to layout...
        layout.addWidget(nameLabel)
        

win_obj = AfricaWindow()

def africa_func():
    win_obj.show()

# Create QAction obj and connect trigger function...
afri_action = QAction("Africa Menu")
afri_action.triggered.connect(africa_func)

# Add menu separator and action...
iface.helpMenu().addSeparator()
iface.helpMenu().addAction(afri_action)
This will add a text menu under the helpMenu that will display a window with our love text on it. If you want this to be under the pluginMenu, just change the name in the last lines.


Other menu names we can use are: helpMenu, editMenu, projectMenu, pluginMenu, rasterMenu, vectorMenu, layerMenu, settingsMenu, viewMenu, webMenu, windowMenu etc.

This can also be added on a sub menu like: newLayerMenu



Project 4: Know Your State (KYS) Game

This is a simple game that will let you familiarize yourself with your state. All you need to do it load you state polygon shapefile layer and provide the name of attribute column that holds the names you want to test your knowledge on.

The correct answers will then be displayed at the end.

# KYS Game...
import random
from PyQt5 import QtTest


# Select active layer
layer.selectByExpression( " state_name = 'Kogi' " )

# Make single selection by expression... (" column_name = 'Value' ")
# layer.selectByExpression(" state_name = 'Kogi' ") 

# THE KYS GAME...
# Get all polygon names into list...
my_list = []
for item in layer.getFeatures():
    # print(item.attributes()[0])
    name = item.attributes()[0]
    my_list.append(name)


i = 1
ans_list = []
for x in range(5):
    print(f'Write down the names of the highlighted states {i}?')
    print('You have 5 seconds to write down your answer. \n')
    QtTest.QTest.qWait(1000)
    
    rand_name = random.choices(my_list)[0]
    layer.selectByExpression(f" state_name = '{rand_name}' ")
    
    QtTest.QTest.qWait(5000)
    ans_list.append(rand_name)
    i += 1

# Display Answer...
print(f'The correct answers are: {ans_list}')

Below, you will see a demo how I played and lost three names out of five.


Of course, there are lots of improvement that need to be implemented to make the game better (I leave the for you to handle 😏).



Conclusion

Hope you have learned something new from these projects. On a final note, lets me show you have to auto run these python codes at each time you launch a new QGIS instance.

Create a file named "startup.py" in the QGIS home directory (C:\Users\pc_username\AppData\Roaming\QGIS\QGIS3). In the this file, we will write the codes above within functions and then connect them to iface.initializationCompleted signal which runs when the main window is loaded.



The only addition to our code is that we have to explicitly import the modules (classes) that we want to use. This is because the code is running from an external environment not in QGIS python console where all this modules (classes) are imported automatically.

There are two ways to import the classes, namely:-

  1. from PyQGIS classes
  2. from PyQt classes

For example, the class QApplication is from PyQt. It equivalent class in PyQGIS is QgsApplication which is just an extension of QApplication to provide access to QGIS specific resources such as theme paths, database paths etc.

They can be imported as follow:-
from PyQt5.QtWidgets import QApplication
from qgis.core import QgsApplication

If you are unsure where the classes are located, you can import all of them like so:-

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

from qgis.core import *
from qgis.gui import *


Lets leave it here, you may consult the documentation for PyQt and PyQGIS for more details.

The code for "startup.py" file should look like below and whenever you start QGIS app it will apply the predefined functions.

from qgis.utils import iface

from PyQt5.QtWidgets import QApplication
from qgis.core import QgsApplication

# from PyQt5.QtWidgets import QApplication, QLabel, QFont, QWidget, QAction
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

from qgis.core import *
from qgis.gui import *
from qgis.utils import *

import os
import requests



# --------------- Define the Function --------------------
def pinkish_func():
    app = QApplication.instance()
    app.setStyleSheet("QWidget {color: blue; background-color: pink;}")


def chucknorris_func():
    try:
        # Get random jokes from the API...
        jokes_url = 'https://api.chucknorris.io/jokes/random'
        joke = requests.get(jokes_url, timeout=10).json()

        # joke.keys()
        # Get the actual joke text from value key...
        joke_txt = joke['value']
        print(joke_txt)

        # Write joke to main window title bar...
        joke_txt = f'*** {joke_txt} ***'
        iface.mainWindow().setWindowTitle(joke_txt)
        
    except Exception:
        # Write joke to main window title bar...
        joke_txt = f"*** ERROR: NO CHUCK NORRIS'S JOKE AVAILABLE ***"
        iface.mainWindow().setWindowTitle(joke_txt)


def afri_love_icon():
    # Construct path to icon svg file...
    icon = 'Africa.svg'
    data_dir = os.path.join(os.path.expanduser('~'), 'Desktop')
    icon_path = os.path.join(data_dir, icon)

    def africa_func():
        print('*** I Love Africa ***')

    # Create QAction obj and connect trigger function...
    action = QAction('African Love')
    action.triggered.connect(africa_func)

    action.setIcon(QIcon(icon_path)) # Set icon to action obj
    iface.addToolBarIcon(action) # Add icon to toolbar




# -----------------------------------
# Connect to iface.initializationCompleted signal when the main window is loaded
iface.initializationCompleted.connect(pinkish_func)
iface.initializationCompleted.connect(chucknorris_func)
iface.initializationCompleted.connect(afri_love_icon)


That is it, have fun!

No comments:

Post a Comment