Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

46 poista kaavamääräysryhmä -napin implementaatio templaattilomakkeeseen #48

Merged
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ the one with the path `.venv\Scripts\python.exe`.
This plugin is distributed under the terms of the [GNU General Public License, version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) license.

See [LICENSE](LICENSE) for more information.

### Attributations
<a href="https://www.flaticon.com/free-icons/open" title="open icons">Open icons created by Smashicons - Flaticon</a>
<a href="https://www.flaticon.com/free-icons/land-use" title="land use icons">Land use icons created by Fusion5085 - Flaticon</a>
2 changes: 2 additions & 0 deletions arho_feature_template/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class UnexpectedNoneError(Exception):
"""Internal QGIS errors that should not be happened"""
63 changes: 63 additions & 0 deletions arho_feature_template/core/new_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from qgis.core import QgsProject, QgsVectorLayer
from qgis.utils import iface

from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan


class NewPlan:
def add_new_plan(self):
# Filtered layers are not editable, so clear filters first.
self.clear_all_filters()

layers = QgsProject.instance().mapLayersByName("Kaava")
if not layers:
iface.messageBar().pushMessage("Error", "Layer 'Kaava' not found", level=3)
return

kaava_layer = layers[0]

if not kaava_layer.isEditable():
kaava_layer.startEditing()

iface.setActiveLayer(kaava_layer)

iface.actionAddFeature().trigger()

# Connect the featureAdded signal
kaava_layer.featureAdded.connect(self.feature_added)

def feature_added(self):
kaava_layer = iface.activeLayer()
kaava_layer.featureAdded.disconnect()
feature_ids_before_commit = kaava_layer.allFeatureIds()
if kaava_layer.isEditable():
if not kaava_layer.commitChanges():
iface.messageBar().pushMessage("Error", "Failed to commit changes to the layer.", level=3)
return
else:
iface.messageBar().pushMessage("Error", "Layer is not editable.", level=3)
return

feature_ids_after_commit = kaava_layer.allFeatureIds()

# Find the new plan.id by comparing fids before and after commit.
new_feature_id = next((fid for fid in feature_ids_after_commit if fid not in feature_ids_before_commit), None)

if new_feature_id is not None:
new_feature = kaava_layer.getFeature(new_feature_id)

if new_feature.isValid():
feature_id_value = new_feature["id"]
update_selected_plan(LandUsePlan(feature_id_value))
else:
iface.messageBar().pushMessage("Error", "Invalid feature retrieved.", level=3)
else:
iface.messageBar().pushMessage("Error", "No new feature was added.", level=3)

def clear_all_filters(self):
"""Clear filters for all vector layers in the project."""
layers = QgsProject.instance().mapLayers().values()

for layer in layers:
if isinstance(layer, QgsVectorLayer):
layer.setSubsetString("")
62 changes: 62 additions & 0 deletions arho_feature_template/core/update_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from dataclasses import dataclass

from qgis.core import QgsMapLayer, QgsProject, QgsVectorLayer
from qgis.utils import iface


# To be extended and moved
@dataclass
class LandUsePlan:
id: str


# To be replaced later
LAYER_PLAN_ID_MAP = {
"Kaava": "id",
"Maankäytön kohteet": "plan_id",
"Muut pisteet": "plan_id",
"Viivat": "plan_id",
"Aluevaraus": "plan_id",
"Osa-alue": "plan_id",
}


def update_selected_plan(new_plan: LandUsePlan):
"""Update the project layers based on the selected land use plan."""
plan_id = new_plan.id

for layer_name, field_name in LAYER_PLAN_ID_MAP.items():
# Set the filter on each layer using the plan_id
set_filter_for_vector_layer(layer_name, field_name, plan_id)


def set_filter_for_vector_layer(layer_name: str, field_name: str, field_value: str):
"""Set a filter for the given vector layer."""
layers = QgsProject.instance().mapLayersByName(layer_name)

if not _check_layer_count(layers):
return

layer = layers[0]

expression = f"\"{field_name}\" = '{field_value}'"

# Apply the filter to the layer
if not layer.setSubsetString(expression):
iface.messageBar().pushMessage("Error", f"Failed to filter layer {layer_name} with query {expression}", level=3)


def _check_layer_count(layers: list) -> bool:
"""Check if any layers are returned."""
if not layers:
iface.messageBar().pushMessage("Error", "ERROR: No layers found with the specified name.", level=3)
return False
return True


def _check_vector_layer(layer: QgsMapLayer) -> bool:
"""Check if the given layer is a vector layer."""
if not isinstance(layer, QgsVectorLayer):
iface.messageBar().pushMessage("Error", f"Layer {layer.name()} is not a vector layer: {type(layer)}", level=3)
return False
return True
145 changes: 145 additions & 0 deletions arho_feature_template/gui/load_plan_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from importlib import resources

from qgis.core import QgsProviderRegistry
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QRegularExpression, QSortFilterProxyModel, Qt
from qgis.PyQt.QtGui import QStandardItem, QStandardItemModel
from qgis.PyQt.QtWidgets import QComboBox, QDialog, QDialogButtonBox, QLineEdit, QMessageBox, QPushButton, QTableView

from arho_feature_template.core.exceptions import UnexpectedNoneError

ui_path = resources.files(__package__) / "load_plan_dialog.ui"

LoadPlanDialogBase, _ = uic.loadUiType(ui_path)


class PlanFilterProxyModel(QSortFilterProxyModel):
def filterAcceptsRow(self, source_row, source_parent): # noqa: N802
model = self.sourceModel()
if not model:
return False

filter_text = self.filterRegularExpression().pattern()
if not filter_text:
return True

for column in range(5):
index = model.index(source_row, column, source_parent)
data = model.data(index)
if data and filter_text.lower() in data.lower():
return True

return False


class LoadPlanDialog(QDialog, LoadPlanDialogBase): # type: ignore
connectionComboBox: QComboBox # noqa: N815
push_button_load: QPushButton
planTableView: QTableView # noqa: N815
searchLineEdit: QLineEdit # noqa: N815
buttonBox: QDialogButtonBox # noqa: N815

def __init__(self, parent, connections):
super().__init__(parent)
self.setupUi(self)

self._selected_plan_id = None

self.buttonBox.rejected.connect(self.reject)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)

self.push_button_load.clicked.connect(self.load_plans)
self.searchLineEdit.textChanged.connect(self.filter_plans)

self.connectionComboBox.addItems(connections)

self.planTableView.setSelectionMode(QTableView.SingleSelection)
self.planTableView.setSelectionBehavior(QTableView.SelectRows)
self.planTableView.selectionModel().selectionChanged.connect(self.on_selection_changed)

self.model = QStandardItemModel()
self.model.setColumnCount(5)
self.model.setHorizontalHeaderLabels(
[
"ID",
"Tuottajan kaavatunnus",
"Nimi",
"Kaavan elinkaaren tila",
"Kaavalaji",
]
)

self.filterProxyModel = PlanFilterProxyModel()
self.filterProxyModel.setSourceModel(self.model)
self.filterProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)

self.planTableView.setModel(self.filterProxyModel)

def load_plans(self):
self.model.removeRows(0, self.model.rowCount())

selected_connection = self.connectionComboBox.currentText()
if not selected_connection:
self.planTableView.setModel(QStandardItemModel())
return

provider_registry = QgsProviderRegistry.instance()
if provider_registry is None:
raise UnexpectedNoneError
postgres_provider_metadata = provider_registry.providerMetadata("postgres")
if postgres_provider_metadata is None:
raise UnexpectedNoneError

try:
connection = postgres_provider_metadata.createConnection(selected_connection)
plans = connection.executeSql("""
SELECT
p.id,
p.producers_plan_identifier,
p.name ->> 'fin' AS name_fin,
l.name ->> 'fin' AS lifecycle_status_fin,
pt.name ->> 'fin' AS plan_type_fin
FROM
hame.plan p
LEFT JOIN
codes.lifecycle_status l
ON
p.lifecycle_status_id = l.id
LEFT JOIN
codes.plan_type pt
ON
p.plan_type_id = pt.id;
""")
for plan in plans:
self.model.appendRow([QStandardItem(column) for column in plan])

except Exception as e: # noqa: BLE001
QMessageBox.critical(self, "Error", f"Failed to load plans: {e}")
self.model.removeRows(0, self.model.rowCount())

def filter_plans(self):
search_text = self.searchLineEdit.text()
if search_text:
search_regex = QRegularExpression(search_text)
self.filterProxyModel.setFilterRegularExpression(search_regex)
else:
self.filterProxyModel.setFilterRegularExpression("")

def on_selection_changed(self):
# Enable the OK button only if a row is selected
selection = self.planTableView.selectionModel().selectedRows()
ok_button = self.buttonBox.button(QDialogButtonBox.Ok)
if selection:
selected_row = selection[0].row()
self._selected_plan_id = self.planTableView.model().index(selected_row, 0).data()
ok_button.setEnabled(True)
else:
self._selected_plan_id = None
ok_button.setEnabled(False)

def get_selected_connection(self):
return self.connectionComboBox.currentText()

def get_selected_plan_id(self):
return self._selected_plan_id
Loading
Loading