Skip to content

Commit fc78f82

Browse files
committed
Save plan and plan outline as JSON.
- Added UI for selecting path for saving JSONs - Added get_plan_json that calls lambda with the active_plan_id. The JSONs are extracted from the response and saved at user defined location. - Changed default lambda port. Refactor get_plan_json. Changed lambda call to use QNetworkRequest and created the required QNetworkProxy. Changed settings to store Proxy host and port. Added lambda url to settings. Cleanup
1 parent 700f1ff commit fc78f82

File tree

7 files changed

+289
-15
lines changed

7 files changed

+289
-15
lines changed

arho_feature_template/core/plan_manager.py

+110-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
1+
import json
2+
13
from qgis.core import QgsExpressionContextUtils, QgsProject, QgsVectorLayer
4+
from qgis.PyQt.QtCore import QByteArray, QUrl
5+
from qgis.PyQt.QtNetwork import QNetworkAccessManager, QNetworkProxy, QNetworkReply, QNetworkRequest
26
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
37
from qgis.utils import iface
48

59
from arho_feature_template.core.update_plan import LandUsePlan, update_selected_plan
610
from arho_feature_template.gui.load_plan_dialog import LoadPlanDialog
11+
from arho_feature_template.gui.serialize_plan import SerializePlan
712
from arho_feature_template.utils.db_utils import get_existing_database_connection_names
8-
from arho_feature_template.utils.misc_utils import get_layer_by_name, handle_unsaved_changes
13+
from arho_feature_template.utils.misc_utils import get_active_plan_id, get_settings, handle_unsaved_changes
914

1015

1116
class PlanManager:
1217
def __init__(self):
13-
self.kaava_layer = get_layer_by_name("Kaava")
18+
# Init network manager
19+
self.network_manager = QNetworkAccessManager()
20+
proxy_host, proxy_port, self.lambda_url = get_settings()
21+
22+
# SOCKS5 Proxy
23+
proxy = QNetworkProxy()
24+
proxy.setType(QNetworkProxy.Socks5Proxy)
25+
proxy.setHostName(proxy_host)
26+
proxy.setPort(int(proxy_port))
27+
self.network_manager.setProxy(proxy)
28+
29+
self.kaava_layer = self.get_layer_by_name("Kaava")
30+
31+
def get_layer_by_name(self, layer_name):
32+
"""Retrieve a layer by name from the project."""
33+
layers = QgsProject.instance().mapLayersByName(layer_name)
34+
if layers:
35+
return layers[0]
36+
iface.messageBar().pushMessage("Error", f"Layer '{layer_name}' not found", level=3)
37+
return None
1438

1539
def add_new_plan(self):
1640
"""Initiate the process to add a new plan to the Kaava layer."""
@@ -98,3 +122,87 @@ def commit_all_editable_layers(self):
98122
for layer in QgsProject.instance().mapLayers().values():
99123
if isinstance(layer, QgsVectorLayer) and layer.isEditable():
100124
layer.commitChanges()
125+
126+
def get_plan_json(self):
127+
"""Serializes plan and plan outline to JSON"""
128+
dialog = SerializePlan()
129+
if dialog.exec_() == QDialog.Accepted:
130+
json_plan_path = dialog.plan_path_edit.text()
131+
json_plan_outline_path = dialog.plan_outline_path_edit.text()
132+
133+
plan_id = get_active_plan_id()
134+
135+
if not plan_id:
136+
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa. Luo tai avaa kaava.")
137+
return
138+
139+
payload = {"action": "get_plans", "plan_uuid": plan_id}
140+
payload_bytes = QByteArray(json.dumps(payload).encode("utf-8"))
141+
142+
request = QNetworkRequest(QUrl(self.lambda_url))
143+
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
144+
145+
reply = self.network_manager.post(request, payload_bytes)
146+
147+
# Connect the finished signal to a lambda to pass extra arguments
148+
reply.finished.connect(lambda: self._handle_response(reply, json_plan_path, json_plan_outline_path))
149+
150+
def _handle_response(self, reply: QNetworkReply, json_plan_path: str, json_plan_outline_path: str):
151+
"""Handle the API response and process the plan and outline data."""
152+
if reply.error() != QNetworkReply.NoError:
153+
error_string = reply.errorString()
154+
QMessageBox.critical(None, "Virhe API kutsussa", f"Virhe: {error_string}")
155+
return
156+
157+
try:
158+
response_data = reply.readAll().data().decode("utf-8")
159+
response_json = json.loads(response_data)
160+
161+
details = response_json.get("details")
162+
if not details:
163+
QMessageBox.warning(None, "Varoitus", "Vastauksesta puuttuu 'details' kenttä.")
164+
return
165+
166+
# Extract plan and write it to file
167+
plan_id = get_active_plan_id()
168+
if not plan_id:
169+
QMessageBox.critical(None, "Virhe", "Ei aktiivista kaavaa.")
170+
return
171+
172+
plan_json = details.get(plan_id)
173+
if not plan_json:
174+
QMessageBox.warning(None, "Varoitus", f"Aktiiviselle kaavalle (id: {plan_id}) ei löydy tietoja.")
175+
return
176+
177+
with open(json_plan_path, "w", encoding="utf-8") as full_file:
178+
json.dump(plan_json, full_file, ensure_ascii=False, indent=2)
179+
180+
# Process the geographicalArea for the outline
181+
geographical_area = plan_json.get("geographicalArea")
182+
if geographical_area:
183+
try:
184+
# Build the structured outline JSON and write to file
185+
outline_json = {
186+
"type": "Feature",
187+
"properties": {"name": "Example Polygon"},
188+
"srid": geographical_area.get("srid"),
189+
"geometry": geographical_area.get("geometry"),
190+
}
191+
192+
with open(json_plan_outline_path, "w", encoding="utf-8") as outline_file:
193+
json.dump(outline_json, outline_file, ensure_ascii=False, indent=2)
194+
195+
QMessageBox.information(None, "Success", "Kaava ja sen ulkorja tallennettu onnistuneesti.")
196+
except KeyError as e:
197+
QMessageBox.critical(
198+
None,
199+
"Virhe",
200+
f"'geographicalArea' ei sisällä vaadittuja tietoja: {e}",
201+
)
202+
else:
203+
QMessageBox.warning(None, "Varoitus", "Kenttä 'geographicalArea' puuttuu kaavan tiedoista.")
204+
except json.JSONDecodeError as e:
205+
QMessageBox.critical(None, "Virhe", f"Vastaus JSON:in purkaminen epäonnistui: {e}")
206+
207+
# Clean up the reply
208+
reply.deleteLater()

arho_feature_template/gui/plugin_settings.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ def load_settings(self):
2222
"""Load settings from QSettings with default values."""
2323
settings = QSettings("ArhoFeatureTemplate")
2424

25-
lambda_host = settings.value("lambda_host", "localhost")
26-
lambda_port = settings.value("lambda_port", "5435")
25+
proxy_host = settings.value("proxy_host", "localhost")
26+
proxy_port = settings.value("proxy_port", "5443")
27+
lambda_url = settings.value("lambda_url", "https://t5w26iqnsf.execute-api.eu-central-1.amazonaws.com/v0/ryhti")
2728

28-
self.hostInput.setText(lambda_host)
29-
self.portInput.setText(lambda_port)
29+
self.hostInput.setText(proxy_host)
30+
self.portInput.setText(proxy_port)
31+
self.lambdaInput.setText(lambda_url)
3032

3133
def save_settings(self):
3234
"""Save settings to QSettings."""
3335
settings = QSettings("ArhoFeatureTemplate")
34-
settings.setValue("lambda_host", self.hostInput.text())
35-
settings.setValue("lambda_port", self.portInput.text())
36+
settings.setValue("proxy_host", self.hostInput.text())
37+
settings.setValue("proxy_port", self.portInput.text())
38+
settings.setValue("lambda_url", self.lambdaInput.text())
3639

3740
self.accept()

arho_feature_template/gui/plugin_settings.ui

+12-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<item>
1818
<widget class="QLabel" name="labelHost">
1919
<property name="text">
20-
<string>Lambdan isäntä:</string>
20+
<string>Proxy isäntä:</string>
2121
</property>
2222
</widget>
2323
</item>
@@ -27,13 +27,23 @@
2727
<item>
2828
<widget class="QLabel" name="labelPort">
2929
<property name="text">
30-
<string>Lambdan portti:</string>
30+
<string>Proxy portti:</string>
3131
</property>
3232
</widget>
3333
</item>
3434
<item>
3535
<widget class="QLineEdit" name="portInput"/>
3636
</item>
37+
<item>
38+
<widget class="QLabel" name="labelLambda">
39+
<property name="text">
40+
<string>Lambdan osoite:</string>
41+
</property>
42+
</widget>
43+
</item>
44+
<item>
45+
<widget class="QLineEdit" name="lambdaInput"/>
46+
</item>
3747
<item>
3848
<widget class="QPushButton" name="saveButton">
3949
<property name="text">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from importlib import resources
2+
3+
from qgis.PyQt import uic
4+
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QFileDialog, QLineEdit, QPushButton
5+
6+
ui_path = resources.files(__package__) / "serialize_plan.ui"
7+
FormClass, _ = uic.loadUiType(ui_path)
8+
9+
10+
class SerializePlan(QDialog, FormClass): # type: ignore
11+
def __init__(self):
12+
super().__init__()
13+
self.setupUi(self)
14+
15+
self.plan_outline_path_edit = self.findChild(QLineEdit, "plan_outline_path_edit")
16+
self.plan_path_edit = self.findChild(QLineEdit, "plan_path_edit")
17+
self.plan_outline_select_button = self.findChild(QPushButton, "plan_outline_select_button")
18+
self.plan_select_button = self.findChild(QPushButton, "plan_select_button")
19+
20+
self.plan_outline_select_button.clicked.connect(self.select_plan_outline_file)
21+
self.plan_select_button.clicked.connect(self.select_plan_file)
22+
23+
# Disable Save button initially
24+
self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
25+
self.buttonBox.accepted.connect(self.accept)
26+
self.buttonBox.rejected.connect(self.reject)
27+
28+
def select_plan_outline_file(self):
29+
file_path, _ = QFileDialog.getSaveFileName(
30+
self, "Valitse kaavan ulkorajan tallennuspolku", "", "JSON Files (*.json)"
31+
)
32+
if file_path:
33+
self.plan_outline_path_edit.setText(file_path)
34+
self.check_inputs()
35+
36+
def select_plan_file(self):
37+
file_path, _ = QFileDialog.getSaveFileName(self, "Valitse kaavan tallennuspolku", "", "JSON Files (*.json)")
38+
if file_path:
39+
self.plan_path_edit.setText(file_path)
40+
self.check_inputs()
41+
42+
def check_inputs(self):
43+
# Enable Save button if both text fields have paths
44+
if self.plan_outline_path_edit.text() and self.plan_path_edit.text():
45+
self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)
46+
else:
47+
self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
48+
49+
def get_paths(self):
50+
return self.plan_outline_path_edit.text(), self.plan_path_edit.text()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>Dialog</class>
4+
<widget class="QDialog" name="Dialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>558</width>
10+
<height>193</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Tallenna kaava JSON</string>
15+
</property>
16+
<widget class="QDialogButtonBox" name="buttonBox">
17+
<property name="geometry">
18+
<rect>
19+
<x>20</x>
20+
<y>150</y>
21+
<width>521</width>
22+
<height>32</height>
23+
</rect>
24+
</property>
25+
<property name="standardButtons">
26+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
27+
</property>
28+
<property name="centerButtons">
29+
<bool>true</bool>
30+
</property>
31+
</widget>
32+
<widget class="QWidget" name="layoutWidget">
33+
<property name="geometry">
34+
<rect>
35+
<x>20</x>
36+
<y>20</y>
37+
<width>521</width>
38+
<height>120</height>
39+
</rect>
40+
</property>
41+
<layout class="QGridLayout" name="gridLayout">
42+
<item row="0" column="0">
43+
<widget class="QLabel" name="plan_outline_label">
44+
<property name="text">
45+
<string>Kaavan ulkorajan tallennuspolku:</string>
46+
</property>
47+
</widget>
48+
</item>
49+
<item row="0" column="1">
50+
<widget class="QLineEdit" name="plan_outline_path_edit">
51+
<property name="readOnly">
52+
<bool>true</bool>
53+
</property>
54+
</widget>
55+
</item>
56+
<item row="0" column="2">
57+
<widget class="QPushButton" name="plan_outline_select_button">
58+
<property name="text">
59+
<string>Valitse...</string>
60+
</property>
61+
</widget>
62+
</item>
63+
<item row="1" column="0">
64+
<widget class="QLabel" name="plan_label">
65+
<property name="text">
66+
<string>Kaavan tallennuspolku:</string>
67+
</property>
68+
</widget>
69+
</item>
70+
<item row="1" column="1">
71+
<widget class="QLineEdit" name="plan_path_edit">
72+
<property name="readOnly">
73+
<bool>true</bool>
74+
</property>
75+
</widget>
76+
</item>
77+
<item row="1" column="2">
78+
<widget class="QPushButton" name="plan_select_button">
79+
<property name="text">
80+
<string>Valitse...</string>
81+
</property>
82+
</widget>
83+
</item>
84+
</layout>
85+
</widget>
86+
</widget>
87+
<resources/>
88+
<connections/>
89+
</ui>

arho_feature_template/plugin.py

+13
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ def initGui(self) -> None: # noqa N802
172172
add_to_toolbar=True,
173173
)
174174

175+
self.serialize_plan_action = self.add_action(
176+
"",
177+
text="Tallenna kaava JSON",
178+
triggered_callback=self.serialize_plan,
179+
add_to_menu=True,
180+
add_to_toolbar=True,
181+
status_tip="Tallenna aktiivinen kaava JSON muodossa",
182+
)
183+
175184
self.plugin_settings_action = self.add_action(
176185
"",
177186
text="Asetukset",
@@ -191,6 +200,10 @@ def add_new_plan(self):
191200
def load_existing_land_use_plan(self):
192201
self.plan_manager.load_land_use_plan()
193202

203+
def serialize_plan(self):
204+
"""Serializes currently active plan."""
205+
self.plan_manager.get_plan_json()
206+
194207
def open_settings(self):
195208
"""Open the plugin settings dialog."""
196209
settings = PluginSettings()

arho_feature_template/utils/misc_utils.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,10 @@ def get_active_plan_id():
106106
return QgsExpressionContextUtils.projectScope(QgsProject.instance()).variable("active_plan_id")
107107

108108

109-
def get_lambda_settings():
110-
"""Retrieve Lambda settings, using defaults if not set."""
109+
def get_settings():
110+
"""Retrieve stored settings, using defaults if not set."""
111111
settings = QSettings("ArhoFeatureTemplate")
112-
lambda_host = settings.value("lambda_host", "localhost")
113-
lambda_port = settings.value("lambda_port", "8083")
114-
return lambda_host, lambda_port
112+
proxy_host = settings.value("proxy_host", "localhost")
113+
proxy_port = settings.value("proxy_port", "5443")
114+
lambda_url = settings.value("lambda_url", "https://t5w26iqnsf.execute-api.eu-central-1.amazonaws.com/v0/ryhti")
115+
return proxy_host, proxy_port, lambda_url

0 commit comments

Comments
 (0)