Category Archives: WTF

Properly setting up your QGIS license

If you want your copy of QGIS display it’s legal licensing status, this is the missing code for you.

Copy this in your QGIS Python script editor (WARNING: DO NOT RUN THIS IN AN IMPORTANT USER PROFILE, I will NOT help you if it breaks something):

import os
from qgis.core import QgsApplication, QgsSettings
from qgis.PyQt.QtGui import QColor, QImage, QPainter, QPen, QStaticText
from qgis.PyQt.QtWidgets import QMessageBox

def install_qgis_license():
    # WARNING: This fucks around with your profiles and stuff!
    # QgsCustomization is not available from Python so just yolo here and write a fresh file if possible
    profile_directory = QgsApplication.qgisSettingsDirPath()
    customization_ini_filepath = profile_directory + "QGIS/QGISCUSTOMIZATION3.ini"

    if os.path.isfile(customization_ini_filepath):
        # ain't gonna be touchin dat!
        text = (
            "QGISCUSTOMIZATION3.ini EXISTS! UNLICENSED HACKING DETECTED!\n"
            "Or: Custom license has been installed already...\n"
            "Anyways, we are not creating a *new* license now ;)"
        )
        messagebox = QMessageBox()
        messagebox.setText(text);
        messagebox.exec()
        return

    # get existing splash image
    splash_path = QgsApplication.splashPath()  # :/images/splash/
    splash_image_file = splash_path + "splash.png"
    splash_image = QImage(splash_image_file)

    # paint new splash image
    new_splash_image = QImage(splash_image)
    painter = QPainter()
    painter.begin(new_splash_image)

    # white bold font plz
    font = QgsApplication.font()
    font.setBold(True)
    painter.setFont(font)
    pen = painter.pen()
    pen.setColor(QColor("white"))
    painter.setPen(pen)

    # place text at appropriate location
    label_text = f"This QGIS©®™ is legally licensed to {os.getlogin()}"
    label_text_rect = painter.boundingRect(0, 0, 0, 0, 0, label_text)  # returns new rect that fits text
    left_offset = new_splash_image.width() - label_text_rect.size().width() - 20
    painter.drawText(left_offset, 840, label_text)

    painter.end()

    # create license dir if necessary
    new_splash_dir_path = profile_directory + "license"
    try:
        os.mkdir(new_splash_dir_path)
    except FileExistsError:
        pass

    save_success = new_splash_image.save(new_splash_dir_path + "/splash.png")
    if save_success:
        print(f"Initialized new QGIS license....")
    else:
        print("Error on QGIS license initialization, this will get reported!")
        return

    # enable license dir for splash image lookup in QGISCUSTOMIZATION3.ini
    with open(customization_ini_filepath, "w") as sink:
        sink.write("[Customization]\n")
        sink.write(f"splashpath={new_splash_dir_path}/")  # must have trailing slash

    # enable loading of QGISCUSTOMIZATION3.ini in user profile
    QgsSettings().setValue(r"UI/Customization/enabled", True)
    
    print("License installed, reboot QGIS to activate!")
    messagebox = QMessageBox()
    messagebox.setText("License installed, restart QGIS now to activate!");
    messagebox.exec()

#install_qgis_license()

Then (if you really want to do it), uncomment the function call in the last line and execute the script. Follow the instructions.

To clean up remove or restore the QGIS/QGISCUSTOMIZATION3.ini file in your profile and remove the license directory from your profile, restore the previous value of UI/Customization/enabled in your profile (just remove the line or disable Settings -> Interface Customization).

If you want to hate yourself in the future, put it in a file called startup.py in QStandardPaths.standardLocations(QStandardPaths.AppDataLocation) aka the directory which contains the profiles directory itself.

BTW: If you end up with QGIS crashing and lines like these in the error output:

...
Warning: QPaintDevice: Cannot destroy paint device that is being painted
QGIS died on signal 11
...

It is probably not a Qt issue that caused the crash. The QPaintDevice warning might just be Qt telling you about your painter being an issue during clean up of the actual crash (which might just be a wrong name or indentation somewhere in your code, cough).

Oakland: All license plate reader data (ALPR)

WTF!

https://data.oaklandnet.com/browse?q=alpr via https://news.ycombinator.com/item?id=10642139

817159 timestamped car plate locations.

oakland scans
oakland selected scans

Pages:

https://data.oaklandnet.com/Public-Safety/All-License-Plate-Reader-Data-ALPR-September-23-20/6dab-n9nd
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-04-01-2014-thru/k76g-27ne
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-3-1-2013-4-1-20/m64r-jeei
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-3-19-2014-3-31-/wy2w-ue82
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-4-1-14-thru-5-3/7axi-hi5i
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-5-13-2012-7-3-2/f28j-9q95
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-7-6-2011-12-19-/7xz6-yzxz
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-8-6-2012-8-12-2/gyu7-qpwz
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-8-26-2012-9-19-/bd9f-4pn8
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-9-15-2012/vwcs-329i” clas
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-10-20-2012-11-2/cyhz-jk8v
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-11-20-2012-12-3/jxx3-67d2
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-12-19-2013-1-31/h4qp-hsyy
https://data.oaklandnet.com/Public-Safety/All-license-plate-reader-data-ALPR-12-23-2010-thru/t7dd-x7dh

grab.sh:

ids=”6dab-n9nd
7axi-hi5i
7xz6-yzxz
bd9f-4pn8
cyhz-jk8v
f28j-9q95
gyu7-qpwz
h4qp-hsyy
jxx3-67d2
k76g-27ne
m64r-jeei
t7dd-x7dh
vwcs-329i
wy2w-ue82″

for id in ${ids}
do
echo “Grabbing ${id}”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.csv?accessType=DOWNLOAD”
# screw excel wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.csv?accessType=DOWNLOAD&bom=true”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.json?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.pdf?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.rdf?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.rss?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.xls?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.xlsx?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.xml?accessType=DOWNLOAD”
wget -a wget.log -x –content-disposition “https://data.oaklandnet.com/api/views/${id}/rows.xml?accessType=DOWNLOAD”
done

exit

Be aware that only the CSV format is guaranteed to have all records. At least that’s what some files say.

$ sed ‘s#,.*##g’ *.csv | sort | uniq | wc -l
817159

sed ‘s#,.*##g’ *.csv | sort | uniq -c | sort -h | tail
grep -h ‘^PLATENUMBER,’ *.csv | sed ‘s#,”(#,#g’ | sed ‘s#)”##’
grep -hEo “\(37.*\)\”” *.csv | sed ‘s#[()” ]*##g’

Happy stalking :(