Skip to content

Commit

Permalink
Switch to QGIS expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustry committed Aug 13, 2024
1 parent 04a4f0f commit 2142eb0
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 206 deletions.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ hide:
This plugin helps to change the datasource's of many layers at once, for example to create different versions of the
same project on different extents.

You can use some variables such as `$myvar` inside the layer datasource template, and then define these variables
You can use some variables such as `@myvar` inside the layer datasource template, and then define these variables
(from a table or from an input vector source).

Finally, you can apply the variables values on every configured layer via the **apply** button, and you can see the
Expand Down
3 changes: 3 additions & 0 deletions dynamic_layers/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__copyright__ = 'Copyright 2024, 3Liz'
__license__ = 'GPL version 3'
__email__ = 'info@3liz.org'
6 changes: 3 additions & 3 deletions dynamic_layers/core/dynamic_layers_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,17 @@ def set_dynamic_project_properties(self, project: QgsProject):

# Title
val = project.readEntry(PLUGIN_SCOPE, PluginProjectProperty.Title)
if val:
if val[1] and val[0]:
self.set_project_property(project, WmsProjectProperty.Title, val[0])

# Shortname
val = project.readEntry(PLUGIN_SCOPE, PluginProjectProperty.ShortName)
if val:
if val[1] and val[0]:
self.set_project_property(project, WmsProjectProperty.ShortName, val[0])

# Abstract
val = project.readEntry(PLUGIN_SCOPE, PluginProjectProperty.Abstract)
if val:
if val[1] and val[0]:
self.set_project_property(project, WmsProjectProperty.Abstract, val[0])

def set_project_property(self, project: QgsProject, project_property: Annotated[str, WmsProjectProperty], val: str):
Expand Down
30 changes: 15 additions & 15 deletions dynamic_layers/core/generate_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,28 @@
__license__ = 'GPL version 3'
__email__ = 'info@3liz.org'

import string

from pathlib import Path
from shutil import copyfile
from typing import List

from qgis.core import (
QgsFeatureRequest,
QgsProcessingFeatureSource,
QgsProcessingFeedback,
QgsProject,
QgsVectorLayer,
)

from dynamic_layers.core.dynamic_layers_engine import DynamicLayersEngine
from dynamic_layers.tools import side_car_files, tr
from dynamic_layers.tools import side_car_files, string_substitution, tr


class GenerateProjects:

def __init__(
self,
project: QgsProject,
coverage: QgsVectorLayer | QgsProcessingFeatureSource,
coverage: QgsVectorLayer,
field: str,
template_destination: string.Template,
expression_destination: str,
destination: Path,
copy_side_car_files: bool,
feedback: QgsProcessingFeedback = None,
Expand All @@ -37,7 +33,7 @@ def __init__(
self.coverage = coverage
self.field = field
self.destination = destination
self.template_destination = template_destination
self.expression_destination = expression_destination
self.copy_side_car_files = copy_side_car_files
self.feedback = feedback

Expand All @@ -51,8 +47,6 @@ def process(self) -> bool:
if not self.destination.exists():
self.destination.mkdir()

fields = self.coverage.fields().names()

request = QgsFeatureRequest()
# noinspection PyUnresolvedReferences
request.setFlags(QgsFeatureRequest.NoGeometry)
Expand All @@ -65,17 +59,23 @@ def process(self) -> bool:
engine.set_dynamic_layers_datasource_from_dict()
engine.set_dynamic_project_properties(self.project)

new_file = self.template_destination.substitute(dict(zip(fields, feature.attributes())))
new_path = f"{self.destination}/{new_file}"
new_file = string_substitution(
input_string=self.expression_destination,
variables={},
project=self.project,
layer=self.coverage,
feature=feature,
)
new_path = Path(f"{self.destination}/{new_file}")
if self.feedback:
self.feedback.pushDebugInfo(tr('Project written to {}').format(new_path))
self.project.setFileName(new_path)
self.feedback.pushDebugInfo(tr('Project written to {}').format(new_path.name))
self.project.setFileName(str(new_path))
self.project.write()
self.project.setFileName(base_path)

if self.copy_side_car_files:
files = side_car_files(Path(base_path))
for a_file in files:
copyfile(a_file, new_path + a_file.suffix)
copyfile(a_file, str(new_path) + a_file.suffix)

return True
16 changes: 10 additions & 6 deletions dynamic_layers/core/layer_datasource_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ def set_dynamic_layer_properties(self, search_and_replace_dictionary: dict = Non
if title != '':
source_title = title

source_title = f"'{source_title}'"

title_template = self.layer.customProperty(CustomProperty.TitleTemplate)
if title_template and title_template.strip() != '':
source_title = title_template.strip()
if title_template and title_template not in ("", "''"):
source_title = title_template

# Search and replace content
self.layer.setTitle(
Expand All @@ -124,9 +126,10 @@ def set_dynamic_layer_properties(self, search_and_replace_dictionary: dict = Non

# Name
source_name = self.layer.name().strip()
source_name = f"'{source_name}'"
name_template = self.layer.customProperty(CustomProperty.NameTemplate)
if name_template and name_template.strip() != '':
source_name = name_template.strip()
if name_template and name_template not in ("", "''"):
source_name = name_template

# Search and replace content
self.layer.setName(
Expand All @@ -140,10 +143,11 @@ def set_dynamic_layer_properties(self, search_and_replace_dictionary: dict = Non
source_abstract = ''
if self.layer.abstract().strip() != '':
source_abstract = self.layer.abstract().strip()
source_abstract = f"'{source_abstract}'"

abstract_template = self.layer.customProperty(CustomProperty.AbstractTemplate)
if abstract_template and abstract_template.strip() != '':
source_abstract = abstract_template.strip()
if abstract_template and abstract_template not in ("", "''"):
source_abstract = abstract_template

self.layer.setAbstract(
string_substitution(
Expand Down
85 changes: 46 additions & 39 deletions dynamic_layers/dynamic_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
QgsApplication,
QgsIconUtils,
QgsMapLayer,
QgsProcessingException,
QgsProject,
)
from qgis.gui import QgisInterface
Expand Down Expand Up @@ -77,6 +78,7 @@ def __init__(self, iface: QgisInterface):

# Create the dialog (after translation) and keep reference
self.dlg = DynamicLayersDialog()
self.is_expression = self.dlg.is_expression

# Layers attribute that can be shown and optionally changed in the plugin
self.layersTable = [
Expand Down Expand Up @@ -469,7 +471,8 @@ def on_cb_datasource_active_change(self):
bg = QtVar.Green
else:
bg = QtVar.Transparent
for i in range(0, len(self.layerPropertiesInputs) - 1):

for i in range(0, len(self.layerPropertiesInputs) - 2):
self.dlg.twLayers.item(row, i).setBackground(bg)

# Change data for the corresponding column in the layers table
Expand Down Expand Up @@ -500,8 +503,11 @@ def on_layer_property_change(self, key: str):
if item['wType'] == 'text':
input_value = item['widget'].text()

if input_value == "''":
input_value = ''

# Record the new value in the project
self.selectedLayer.setCustomProperty(item['xml'], input_value)
self.selectedLayer.setCustomProperty(item['xml'], input_value.strip())
self.project.setDirty(True)

def on_copy_from_layer(self):
Expand Down Expand Up @@ -549,12 +555,14 @@ def on_copy_from_layer(self):
return

# Set the dynamic datasource content input
self.dlg.dynamicDatasourceContent.setPlainText(uri)
self.dlg.dynamicDatasourceContent.setPlainText(f"'{uri}'" if self.is_expression else uri)

# Set templates for title and abstract
self.dlg.abstractTemplate.setPlainText(abstract)
self.dlg.titleTemplate.setText(title)
self.dlg.dynamic_name_content.setText(name)
self.dlg.abstractTemplate.setPlainText(f"'{abstract}'" if self.is_expression else abstract)

self.dlg.titleTemplate.setText(f"'{title}'" if self.is_expression else title)

self.dlg.dynamic_name_content.setText(f"'{name}'" if self.is_expression else name)

##
# Variables tab
Expand Down Expand Up @@ -755,8 +763,8 @@ def on_copy_from_project_clicked(self):
if not self.project.readEntry(WmsProjectProperty.Capabilities, "/")[1]:
self.project.writeEntry(WmsProjectProperty.Capabilities, "/", True)

self.dlg.inProjectTitle.setText(p_title)
self.dlg.inProjectAbstract.setText(p_abstract)
self.dlg.inProjectTitle.setText(f"'{p_title}'" if self.is_expression else p_title)
self.dlg.inProjectAbstract.setText(f"'{p_abstract}'" if self.is_expression else p_abstract)

def on_project_property_changed(self, prop: str) -> str | None:
"""
Expand Down Expand Up @@ -825,46 +833,45 @@ def on_apply_variables_clicked(self):
self.dlg.message_bar.pushCritical(self.tr("Fail"), self.tr("Initialisation was not finished"))
return

with OverrideCursor(QtVar.WaitCursor):
try:
with OverrideCursor(QtVar.WaitCursor):

# Use the engine class to do the job
engine = DynamicLayersEngine()
# Use the engine class to do the job
engine = DynamicLayersEngine()

# Set the dynamic layers list
engine.set_dynamic_layers_from_project(self.project)
# Set the dynamic layers list
engine.set_dynamic_layers_from_project(self.project)

# Set search and replace dictionary
# Collect variables names and values
if self.dlg.radio_variables_from_table.isChecked():
search_and_replace_dictionary = {}
for row in range(self.dlg.twVariableList.rowCount()):
v_name = self.dlg.twVariableList.item(row, 0).data(QtVar.EditRole)
v_value = self.dlg.twVariableList.item(row, 1).data(QtVar.EditRole)
search_and_replace_dictionary[v_name] = v_value
engine.search_and_replace_dictionary = search_and_replace_dictionary
else:
layer = self.dlg.inVariableSourceLayer.currentLayer()
exp = self.dlg.inVariableSourceLayerExpression.text()
engine.set_search_and_replace_dictionary_from_layer(layer, exp)
# Set search and replace dictionary
# Collect variables names and values
if self.dlg.is_table_variable_based:
engine.search_and_replace_dictionary = self.dlg.variables()
else:
layer = self.dlg.inVariableSourceLayer.currentLayer()
exp = self.dlg.inVariableSourceLayerExpression.text()
engine.set_search_and_replace_dictionary_from_layer(layer, exp)

# Change layers datasource
engine.set_dynamic_layers_datasource_from_dict()
# Change layers datasource
engine.set_dynamic_layers_datasource_from_dict()

# Set project properties
engine.set_dynamic_project_properties(self.project)
# Set project properties
engine.set_dynamic_project_properties(self.project)

# Set extent layer
engine.extent_layer = self.dlg.inExtentLayer.currentLayer()
# Set extent layer
engine.extent_layer = self.dlg.inExtentLayer.currentLayer()

# Set extent margin
engine.extent_margin = self.dlg.inExtentMargin.value()
# Set extent margin
engine.extent_margin = self.dlg.inExtentMargin.value()

# Set new extent
engine.set_project_extent(self.project)
# Set new extent
engine.set_project_extent(self.project)
except QgsProcessingException as e:
self.dlg.message_bar.pushCritical(self.tr("Parsing expression error"), str(e))
return

# Set project as dirty
self.project.setDirty(True)
self.dlg.message_bar.pushSuccess("👍", self.tr("Current project has been updated"))
# Set project as dirty
self.project.setDirty(True)
self.dlg.message_bar.pushSuccess("👍", self.tr("Current project has been updated"))

@staticmethod
def generate_projects_clicked():
Expand Down
Loading

0 comments on commit 2142eb0

Please sign in to comment.