Usage
Launch custom QgisApp¶
The QgisApp is necessary for processing algorithms and CRSHandler.reprojection.
Qgis python bindings are enough for other functionnalities
from pyqgis_wrapper.utils.misc import find_module_path
from pyqgis_wrapper.application import QgisApp
# Usually on unbuntu :
search_start = "/usr/share/qgis/python"
module_dir = find_module_path(search_start, parent_name="plugins", module="processing")
# On windows:
# search_start = r"C:\OSGeo4W\apps\qgis-ltr\python\plugins\processing"
# module_dir = find_module_path(search_start, "core")
qgis = QgisApp(module_dir, use_canvas=True)
Logger usage¶
Base logger should be instantiate using the LoggerMixin.set_up_logger method. All the child logger can then be instantiated using create_child_logger function
from pyqgis_wrapper.utils import LoggerMixin, create_child_logger
from qgis.core import QgsProcessingFeedback
import logging
# QgisLogger use the logging library
# You can use LoggerMixin to define the root logger within your plugin.
# All logger will inherit from it.
class processInAnotherSpace(): # path: name_of_root/algorithm...
def __init__(self):
self.logger = create_child_logger("pyqgis_wrapper.module") # intended for __name__
def do_something(self):
self.logger.info(f"My parent is: {self.logger.parent.name}")
self.logger.debug("This will not show in do_something")
def do_other_things(self):
self.logger.debug("But in do_other_things")
def extra_things(self):
self.logger.setLevel(logging.WARNING)
self.logger.debug("You can also work on child")
class processingAlgorithm(LoggerMixin):
def __init__(self):
self.set_up_logger("pyqgis_wrapper", level=logging.INFO)
def processingAlgorithm(self, feedback=QgsProcessingFeedback):
# Init logger with this level, you can provide a feedback if withing QGIS
# do your things
alg = processInAnotherSpace()
alg.do_something()
self.set_level(logging.DEBUG) # Update root and child if you want to enable debug mode for example.
alg.do_other_things()
alg.extra_things()
# You can log exception, if level is logging.DEBUG the entire stack trace will be returned
# self.set_level(logging.DEBUG)
# try:
# error
# except NameError as e:
# self.root_logger.log_exception(e)
processing_alg = processingAlgorithm()
processing_alg.processingAlgorithm()
# For our usage in this tutorial we define the levle of root logger as DEBUG
# To show operations on the object
17:27:31 [INFO] My parent is: pyqgis_wrapper 17:27:31 [DEBUG] But in do_other_things
Field creation¶
from pyqgis_wrapper.field import FieldFactory
# Create a bunch of fields for a future layer
fields_definition = [{"type_id": "bool", "name": "isValid"}, {"type_id": "int32", "name": "id"}]
fields = FieldFactory.create(fields_definition)
fields.toList() # Ready to add into a layer
[<QgsField: isValid (Boolean)>, <QgsField: id (Integer)>]
# Create a single field for later use
field = FieldFactory.create(type_id="float", name="area")
field
<QgsField: area (Double)>
Vector layer creation and manipulation¶
The builder allows to create a layer from scratch or to copy a part of an existing layer
All managers accept either a single value or an iterable of values.
E.g: To delete a field the manager will accept either the name or a list of names (str | List[str])
from pyqgis_wrapper.layer import VectorLayerBuilder, VectorLayerManager
vector_builder = VectorLayerBuilder()
# Edit session set to False works directly on the data provider. Set to True it'll use the edit buffer.
vector_manager = VectorLayerManager(edit_session=False)
# The builder methods can be chained
layer = vector_builder.configure(crs=2154, geometry="polygon", fields=fields).build().load() # You can use .save to save the file at the given path
# If you want to remove internal reference to delete a layer you can call:
vector_builder.clear()
# Or reset all internal state:
vector_builder.reset()
# Copy a layer without the features but with the fields
another_layer = vector_builder.from_layer(layer, copy_feat=False, copy_fields=True, request=None).build().load()
Feature manager¶
from qgis.core import QgsGeometry, QgsPointXY, QgsFeature
# Add feature
geom = QgsGeometry().fromPolygonXY(
[
[
QgsPointXY(0, 2),
QgsPointXY(2, 2),
QgsPointXY(2, 0),
QgsPointXY(0, 0),
]
]
)
feature_1 = QgsFeature(fields)
feature_1.setGeometry(geom)
feature_1.setId(1)
geom = QgsGeometry().fromPolygonXY(
[
[
QgsPointXY(0, 10),
QgsPointXY(10, 10),
QgsPointXY(10, 11),
QgsPointXY(0, 11),
]
]
)
feature_2 = QgsFeature(fields)
feature_2.setId(2)
feature_2.setGeometry(geom)
success = vector_manager.feature.add(layer, [feature_1, feature_2])
# Update a feature
feature_2.setGeometry(feature_1.geometry())
success = vector_manager.feature.update(layer, feature_2)
# Remove one
success = vector_manager.feature.delete(layer, feature_2.id())
2025-12-04 16:01:01 [DEBUG] Features were added successfully. 2025-12-04 16:01:01 [DEBUG] Feature n°2 updated successfully. 2025-12-04 16:01:01 [DEBUG] Feature n°2 deleted successfully.
Field and attribute manager¶
# Add the previosuly created area field
field_index = vector_manager.field.add(layer, field)
# Add values to the area field
attr_map = {f.id():{field_index: f.geometry().area()} for f in layer.getFeatures()}
success = vector_manager.attribute.update(layer, attr_map)
# isValid field deletion
success = vector_manager.field.delete(layer, "isValid")
2025-12-04 16:01:04 [DEBUG] Field 'area' added successfully. 2025-12-04 16:01:04 [DEBUG] Field 'area' of feature n°1 updated successfully. 2025-12-04 16:01:04 [DEBUG] Field 'isValid' deleted successfully.
Geometry manager¶
geom = QgsGeometry().fromPolygonXY(
[
[
QgsPointXY(1, 2),
QgsPointXY(2, 2),
QgsPointXY(2, 1),
QgsPointXY(1, 1),
]
]
)
geom_map = {feature_1.id(): geom}
success = vector_manager.geometry.update(layer, geom_map)
2025-12-04 16:01:05 [DEBUG] Geometry of feature n°1 updated successfully.
Spatial queries¶
The SpatialPredicate class allows to check a source against a list of candidates. Candidates cna be QgsFeature, QgsGeometry a list of QgsFeature or QgsGeometry or a QgsvectorLayer An optionnal request can be used if a QgsVectorLayer is provided to select a subset of features.
The compute method will return the number of canddiates that satisfy the query as well as the features.
from pyqgis_wrapper.spatial_predicate import SpatialPredicate, PredicateType
count, features = SpatialPredicate.compute(feature_1, feature_2, predicate="intersects")
count, features
(1, [<qgis._core.QgsFeature at 0x78757ec11eb0>])
count, features = SpatialPredicate.compute(geom, layer, predicate=PredicateType.EQUALS)
count, features
(1, [<qgis._core.QgsFeature at 0x78757ece00e0>])
When using the same candidates multiple times the creation of the spatial index can be costly. You can then instantiate the SpatialPredicate class and then run it.
spatial_predicate = SpatialPredicate(layer)
for feature in layer.getFeatures():
count, features = spatial_predicate.run(feature, predicate="intersects")
print(count)
Raster layer creation and manipulation¶
The builder allows to create a layer from scratch or to copy a part of an existing layer.
Bands numerotation use GDAL style (start at 1).
from qgis.core import QgsRectangle
from pyqgis_wrapper.layer import RasterLayerBuilder, RasterLayerManager
import numpy as np
raster_builder = RasterLayerBuilder()
raster_manager = RasterLayerManager()
red = np.tile(np.linspace(0, 255, 100, dtype=np.uint16), (100, 1))
green = np.tile(np.linspace(0, 255, 100, dtype=np.uint16), (100, 1)).T
blue = np.full((100, 100), 80, dtype=np.uint8)
array = np.dstack((red, green, blue))
# Contrary to the vector_builder we have to start with save as it does not handle memory layer.
# All array bands, band argument accepts slice or int
raster_1 = raster_builder.save(layer_name="raster_1").configure(crs=2154 ,dtype=array.dtype, resolution=1 ,extent=QgsRectangle(0,0,100,100), band=None, array=array).build().load()
# Copy a part of the first raster
raster_2 = raster_builder.save(layer_name="raster_2").from_layer(raster_1, extent=QgsRectangle(0,0,50,50), band=None).build().load()
Array and pixel manager¶
When updating a raster be carefull to match the dtype form the incoming array with the raster.
# The extent argument allows to only read a raster part. You can use, for example, a geometry boundingbox
array = raster_manager.array.read(raster_1, band=1, extent=QgsRectangle(0,0,10,10))
array[array != 0] = 9999
raster_1 = raster_manager.array.update(raster_1, array, band=1, extent=QgsRectangle(0,0,10,10))
value = raster_manager.pixel.read(raster_1, point=QgsPointXY(5,5), band=1)
value
9999.0
Metadata manager¶
raster_manager.metadata.set_crs(raster_1, 4326) # Warning: It does not reproject the layer.
layer_crs, provider_crs = raster_manager.metadata.get_crs(raster_1)
layer_crs, provider_crs
(<QgsCoordinateReferenceSystem: EPSG:4326>, <QgsCoordinateReferenceSystem: EPSG:2154>)
raster_manager.metadata.set_src_no_data(raster_1, band=1, value=9999, activate=True)
no_data = raster_manager.metadata.get_src_no_data(raster_1, band=1)
no_data
9999.0
raster_manager.metadata.set_usr_no_data(raster_1, band=1, value=[1,255]) # User no data can be a signel value or a range with the bounds included
no_data = raster_manager.metadata.get_usr_no_data(raster_1, band=1)
no_data
[1.0, 255.0]
band_map = {1:"IR", 2:"Red", 3:"NDVI"}
raster_manager.metadata.set_band_name(raster_1, band_map)
bands = raster_manager.metadata.get_band_name(raster_1)
bands
{1: 'IR', 2: 'Red', 3: 'NDVI'}
Summary manager¶
Allows to fetch the raster histogram and descriptive statistics.
import matplotlib.pyplot as plt
# If bins = 0 the number will be automatically set
# We can use an extent argument for selecting a subset or use a sample size to do a random sampling.
frequency, bin_edges = raster_manager.summary.histogram(raster_1, band=2, bins=0, min=0, max=255, extent=QgsRectangle(50,50,100,100))
plt.bar(bin_edges[:-1], frequency, width=(bin_edges[1]-bin_edges[0]), align='edge')
stats = raster_manager.summary.statistics(raster_1, band=2, extent=QgsRectangle(50,50,100,100))
stats # min, max, mean, stdev, pixel count
(0.0, 126.0, 62.64, 37.17, 10000)
Projection and reprojection¶
The reprojection will reproject the layer if :
- A target_crs is provided,
- No target_crs is provided but the layer have a geographic CRS.
- It'll try to use a preset in a JSON as a target_crs by checking the layer extent;
- It'll build the corresponding UTM CRS if no preset is found.
from pyqgis_wrapper.projection import CRSHandler
# Used internally by most builder and manager
crs = CRSHandler.build_crs(2154)
crs = CRSHandler.build_crs("2154")
crs = CRSHandler.build_crs("EPSG:2154")
reprojected = CRSHandler.reproject(layer, target_crs=4326)
reprojected = CRSHandler.reproject(reprojected)
2025-12-04 16:01:43 [INFO] Layer reprojected to EPSG:4326 2025-12-04 16:01:43 [INFO] Using fallback UTM CRS: EPSG:32730 2025-12-04 16:01:43 [INFO] Layer reprojected to EPSG:32730
Processing¶
from pyqgis_wrapper.processing import AlgorithmFactory
# if you are unsure of the name of the alg you can use the fetch method
# provider will resstrict the search to the provider name (native, gdal, ...)
# An exact match needs a provider name to work. Either directly in the name parameter (e.g "native:buffer") or in the provider parameter
ids, short_description = AlgorithmFactory.fetch(name="buffer", provider=None, exact_match=False)
ids
['gdal:buffervectors', 'gdal:onesidebuffer', 'grass:r.buffer', 'grass:r.buffer.lowmem', 'grass:v.buffer', 'native:buffer', 'native:bufferbym', 'native:multiringconstantbuffer', 'native:singlesidedbuffer', 'native:taperedbuffer', 'native:wedgebuffers', 'qgis:variabledistancebuffer']
When you have the correct algorithm id, build the algorithm and the parameters as following.
The build_opt argument allows to output the optional parameters or not.
The required parameters will be highlighted with REQUIRED
bufferAlgorithm = AlgorithmFactory.build("native:buffer")
parameters = bufferAlgorithm.build_parameters(build_opt=False, output_type="memory:")
parameters
{'INPUT': '**REQUIRED**',
'DISTANCE': 10,
'SEGMENTS': 5,
'END_CAP_STYLE': 0,
'JOIN_STYLE': 0,
'MITER_LIMIT': 2,
'DISSOLVE': False,
'SEPARATE_DISJOINT': False,
'OUTPUT': 'memory:'}
parameters.update({"INPUT": layer})
results = bufferAlgorithm.apply(parameters)
results
{'OUTPUT': <QgsVectorLayer: 'output' (memory)>}
You can also build and run the algorithm at the same time if you know the parameters name that you want or need to update. The build_and_run method accepts a dictionnary of parameters or keywords arguments. The keywords arguments are case insensitive.
results = bufferAlgorithm.build_and_run(build_opt=False, INPUT=layer)
results
{'OUTPUT': <QgsVectorLayer: 'Buffered' (memory)>}
Use a custom canvas¶
With QgisApp you can load your results in a minimalist canvas.
qgis.canvas.load_layers([layer, raster_1]) # Or a single layer
qgis.canvas.show
Exit QgisApp¶
qgis.exit