QGIS Planet Tags
The latest releases of MovingPandas and Trajectools come with many “under the hood” changes that aim to make your movement analytics faster:
Let’s have a look at some example performance measurements!
The ValueChangeSplitter splits trajectories when it detects a value change in the specified column. This is useful, for example, to split up public trajectories that contain a “next_stop” column.
The following graph shows ValueChangeSplitter runtimes for different minimum trajectory length settings (from 0 to 1km, 100km, and 10,000km):
We see that the new, lazy geometry column initialization outperforms the old original code in all cases (e.g. 57% runtime reduction for 1km), except for the worst-case scenario, when the original implementation discards all trajectories as too short right from the start. (For most use cases, min_length will be set to rather small values to avoid creation of undesired short trajectory fragments, similar to sliver polygons in classic geometry operations.)
Additionally, we can engage multiprocessing by setting the n_processes
parameter, e.g. to the number of CPUs to achieve further speedup:
By applying all above-mentioned speedup techniques, Trajectools is now considerably faster. For example, the following runtime reductions can be achieved by deactivating the “Add movement metrics (speed, direction)” option in the algorithm dialog:
I have also updated the default trajectory points output style. It now uses a graduated renderer to visualize the speed values (if they have been calculated) instead of the previously used data-defined override. This makes the style faster to customize and provides a user-friendly legend:
For more infos, have a look at:
Enjoy the latest performance increases!
PT | EN
As I was preparing a QGIS Project to read a database structured according to the new rules and technical specifications for the Portuguese Cartography, I started to configure the editing forms for several layers, so that:
Basically, I wanted something like this:
Let me say that, in PostGIS layers, QGIS does a great job in figuring out the best widget to use for each field, as well as the constraints to apply. Which is a great help. Nevertheless, some need some extra configuration.
If I had only a few layers and fields, I would have done them all by hand, but after the 5th layer my personal mantra started to chime in:
“If you are using a computer to perform a repetitive manual task, you are doing it wrong!”
So, I began to think how could I configure the layers and fields more systematically. After some research and trial and error, I came up with the following PyQGIS functions.
The identifier field (“identificador”) is automatically generated by the database. Therefore, the user shouldn’t edit it. So I had better make it read only
To make all the identifier fields read-only, I used the following code.
def field_readonly(layer, fieldname, option = True): fields = layer.fields() field_idx = fields.indexOf(fieldname) if field_idx >= 0: form_config = layer.editFormConfig() form_config.setReadOnly(field_idx, option) layer.setEditFormConfig(form_config) # Example for the field "identificador" project = QgsProject.instance() layers = project.mapLayers() for layer in layers.values(): field_readonly(layer,'identificador')
The date fields are configured automatically, but the default widget setting only outputs the date, and not date-time, as the rules required.
I started by setting a field in a layer exactly how I wanted, then I tried to figure out how those setting were saved in PyQGIS using the Python console:
>>>layer = iface.mapCanvas().currentLayer() >>>layer.fields().indexOf('inicio_objeto') 1 >>>field = layer.fields()[1] >>>field.editorWidgetSetup().type() 'DateTime' >>>field.editorWidgetSetup().config() {'allow_null': True, 'calendar_popup': True, 'display_format': 'yyyy-MM-dd HH:mm:ss', 'field_format': 'yyyy-MM-dd HH:mm:ss', 'field_iso_format': False}
Knowing this, I was able to create a function that allows configuring a field in a layer using the exact same settings, and apply it to all layers.
def field_to_datetime(layer, fieldname): config = {'allow_null': True, 'calendar_popup': True, 'display_format': 'yyyy-MM-dd HH:mm:ss', 'field_format': 'yyyy-MM-dd HH:mm:ss', 'field_iso_format': False} type = 'Datetime' fields = layer.fields() field_idx = fields.indexOf(fieldname) if field_idx >= 0: widget_setup = QgsEditorWidgetSetup(type,config) layer.setEditorWidgetSetup(field_idx, widget_setup) # Example applied to "inicio_objeto" e "fim_objeto" for layer in layers.values(): field_to_datetime(layer,'inicio_objeto') field_to_datetime(layer,'fim_objeto')
In the data model, many tables have fields that only allow a limited number of values. Those values are referenced to other tables, the Foreign keys.
In these cases, it’s quite helpful to use a Value Relation widget. To configure fields with it in a programmatic way, it’s quite similar to the earlier example, where we first neet to set an example and see how it’s stored, but in this case, each field has a slightly different settings
Luckily, whoever designed the data model, did a favor to us all by giving the same name to the fields and the related tables, making it possible to automatically adapt the settings for each case.
The function stars by gathering all fields in which the name starts with ‘valor_’ (value). Then, iterating over those fields, adapts the configuration to use the reference layer that as the same name as the field.
def field_to_value_relation(layer): fields = layer.fields() pattern = re.compile(r'^valor_') fields_valor = [field for field in fields if pattern.match(field.name())] if len(fields_valor) > 0: config = {'AllowMulti': False, 'AllowNull': True, 'FilterExpression': '', 'Key': 'identificador', 'Layer': '', 'NofColumns': 1, 'OrderByValue': False, 'UseCompleter': False, 'Value': 'descricao'} for field in fields_valor: field_idx = fields.indexOf(field.name()) if field_idx >= 0: print(field) try: target_layer = QgsProject.instance().mapLayersByName(field.name())[0] config['Layer'] = target_layer.id() widget_setup = QgsEditorWidgetSetup('ValueRelation',config) layer.setEditorWidgetSetup(field_idx, widget_setup) except: pass else: return False else: return False return True # Correr função em todas as camadas for layer in layers.values(): field_to_value_relation(layer)
In a relatively quick way, I was able to set all the project’s layers with the widgets I needed.
This seems to me like the tip of the iceberg. If one has the need, with some search and patience, other configurations can be changed using PyQGIS. Therefore, think twice before embarking in configuring a big project, layer by layer, field by fields.