
4.13 Lesson 4 Practice Exercise
The focus in this lesson has been on object-oriented programming in Python and applying it within the QGIS environment to create GUI-based programs and plugins. In the only practice exercise of this lesson, we are going to apply the concepts of self-defined classes, inheritance, and overriding methods to build a standalone GIS tool based on the qgis package. This will be significantly simpler than the bus tracker walkthrough and is intended to prepare you for this lesson's homework assignment.
Here is the task: You have been given a .csv file that contains observations of five different animals in Kruger National Park. Each row in the .csv file contains a unique ID for the observed animal and the latitude and longitude coordinates of the observation, in that order. The observations are ordered chronologically. The test file we will be working with has just the following nine rows. Please download the L4exercise_data.zip file containing this data.
123AD127,-23.965517,31.629621 183AE121,-23.921094,31.688953 223FF097,-23.876783,31.661707 183AE121,-23.876783,31.661707 123AD121,-23.961818,31.694983 223FF097,-24.083749,31.824532 123AD127,-24.083749,31.824532 873TF129,-24.040581,31.426711 123AD127,-24.006232,31.428593
The goal is to write a standalone qgis script that produces a GeoPackage file with a point feature for just the first observation of each animal occurring in the .csv file. The result when opened in QGIS should look like this:

You have already produced some code that reads the data from the file into a pandas dataframe stored in variable data. You also want to reuse a class PointObject that you already have for representing point objects with lat and lon coordinates and that has a method called toQgsFeature(…) that is able to produce and return a QgsFeature (see again Section 4.5.3) for a point object of this class.
import qgis import sys, os import pandas as pd # create pandas data frame from input data data = pd.read_csv(r"C:\489\L4\exercise\L4exercise_data.csv") class PointObject(): # constructor for creating PointObject instances with lon/lat instance variables def __init__(self, lat, lon): self.lon = lon self.lat = lat # methods for creating QgsFeature object from a PointObject instance def toQgsFeature(self): feat = qgis.core.QgsFeature() feat.setGeometry(qgis.core.QgsGeometry.fromPointXY(qgis.core.QgsPointXY(self.lon, self.lat))) return feat firstObservations = [] # for storing objects of class pointWithID firstObservationsFeatures = [] # for storing objects of class QgsFeature
When you look at method toQgsFeature(), you will see that it creates a new QgsFeature (see Section 4.5.3), sets the geometry of the feature to a point with the given longitude and latitude coordinates, and then returns the feature. Since PointObject does not have any further attributes, no attributes are defined for the created QgsFeature object.
Your next step is to write a new class called PointWithID that is derived from the class PointObject and that also stores the unique animal ID in an instance variable. You also want to override the definition of toQgsFeature() in this derived class (see again Section 4.7), so that it also uses setAttributes(…) to make the ID of the animal an attribute of the produced QgsFeature object. To do this, you can first call the toQgsFeature() method of the base class PointObject with the command:
super().toQgsFeature()
… and then take the QgsFeature object returned from this call and set the ID attribute for it with setAttributes(…).
Next, you want to override the == ( __eq__ ) operator for PointWithID so that two objects of that class are considered equal if their ID instance variable are the same. This will allow you to store the PointWithID objects created in a list firstObservations and check whether the list already contains an observation for the animal in a given PointWithID object in variable pointWithID with the expression:
pointWithID in firstObservations
To override the == operator, class PointWithID needs to be given its own definition of the __eq__() method as shown in Section 4.6.2.
What you need to do in this exercise is:
- Define the class PointWithID according to the specification above.
- Add the code needed for making this a standalone qgis program (Sections 4.5.4 and 4.10.2.4).
- Implement a loop that goes through the rows of the pandas dataframe (with data.itertuples(), Section 3.8.2) and creates an object of the class PointWithID for the given row, then adds this object to list firstObervations, unless the list already contains an object with the same animal ID.
- Add the code for creating the new point layer with EPSG:4326 as the CRS.
- Make firstObservationFeatures a list with a QgsFeature object for each PointWithID object in list firstObservations using the overridden toQgsFeature() method. Then add all features from that new list to the new layer (Section 4.5.3).
- Finally, write the layer to a new GeoPackage file and check whether you get the same result as shown in the image above.