Skip to content

Commit 6e2e202

Browse files
committed
fix: adding widget to paint stratigraphic order onto geology shapefile. For debugging stratraphic order
1 parent fdebe4f commit 6e2e202

File tree

5 files changed

+313
-450
lines changed

5 files changed

+313
-450
lines changed

loopstructural/gui/map2loop_tools/paint_stratigraphic_order_widget.py

Lines changed: 197 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from loopstructural.toolbelt.preferences import PlgOptionsManager
1010

11+
from ...main.m2l_api import paint_stratigraphic_order
12+
1113

1214
class PaintStratigraphicOrderWidget(QWidget):
1315
"""Widget for painting stratigraphic order or cumulative thickness onto polygons.
@@ -39,7 +41,8 @@ def __init__(self, parent=None, data_manager=None, debug_manager=None):
3941
# Configure layer filters programmatically
4042
try:
4143
self.geologyLayerComboBox.setFilters(QgsMapLayerProxyModel.PolygonLayer)
42-
self.stratColumnLayerComboBox.setFilters(QgsMapLayerProxyModel.NoGeometry)
44+
# stratigraphic column layer removed from UI
45+
pass
4346
except Exception:
4447
# If QGIS isn't available, skip filter setup
4548
pass
@@ -48,9 +51,27 @@ def __init__(self, parent=None, data_manager=None, debug_manager=None):
4851
self.paint_modes = ["Stratigraphic Order (0=youngest)", "Cumulative Thickness"]
4952
self.paintModeComboBox.addItems(self.paint_modes)
5053

54+
# New UI: duplicate layer and color ramp
55+
try:
56+
# Populate colour ramps from default style
57+
from qgis.core import QgsStyle
58+
59+
ramps = QgsStyle().defaultStyle().colorRampNames()
60+
self.colorRampComboBox.addItems(sorted(ramps))
61+
except Exception as e:
62+
if self._debug.is_debug():
63+
raise e
64+
# if QGIS unavailable, leave empty
65+
pass
66+
67+
# Default: no duplication
68+
try:
69+
self.duplicateLayerCheckBox.setChecked(False)
70+
except Exception:
71+
pass
72+
5173
# Connect signals
5274
self.geologyLayerComboBox.layerChanged.connect(self._on_geology_layer_changed)
53-
self.stratColumnLayerComboBox.layerChanged.connect(self._on_strat_column_layer_changed)
5475
self.runButton.clicked.connect(self._run_painter)
5576

5677
# Set up field combo boxes
@@ -112,7 +133,8 @@ def _log_params(self, context_label: str):
112133
def _setup_field_combo_boxes(self):
113134
"""Set up field combo boxes based on current layers."""
114135
self._on_geology_layer_changed()
115-
self._on_strat_column_layer_changed()
136+
# stratigraphic column layer removed from UI
137+
pass
116138

117139
def _on_geology_layer_changed(self):
118140
"""Update unit name field combo box when geology layer changes."""
@@ -127,147 +149,189 @@ def _on_geology_layer_changed(self):
127149
self.unitNameFieldComboBox.setField(common_name)
128150
break
129151

130-
def _on_strat_column_layer_changed(self):
131-
"""Update stratigraphic column field combo boxes when layer changes."""
132-
strat_layer = self.stratColumnLayerComboBox.currentLayer()
133-
self.stratUnitFieldComboBox.setLayer(strat_layer)
134-
self.stratThicknessFieldComboBox.setLayer(strat_layer)
135-
136-
# Try to auto-select common field names
137-
if strat_layer:
138-
field_names = [field.name() for field in strat_layer.fields()]
139-
140-
# Unit name field
141-
for common_name in ['unit_name', 'name', 'UNITNAME', 'unitname']:
142-
if common_name in field_names:
143-
self.stratUnitFieldComboBox.setField(common_name)
144-
break
145-
146-
# Thickness field
147-
for common_name in ['thickness', 'THICKNESS', 'thick']:
148-
if common_name in field_names:
149-
self.stratThicknessFieldComboBox.setField(common_name)
150-
break
151-
152152
def _run_painter(self):
153153
"""Run the paint stratigraphic order algorithm."""
154-
from qgis import processing
155154

156-
self._log_params("paint_strat_order_widget_run")
157-
158-
# Validate inputs
159-
if not self.geologyLayerComboBox.currentLayer():
160-
QMessageBox.warning(self, "Missing Input", "Please select a geology polygon layer.")
161-
return
162-
163-
if not self.stratColumnLayerComboBox.currentLayer():
164-
QMessageBox.warning(self, "Missing Input", "Please select a stratigraphic column layer.")
165-
return
166-
167-
if not self.unitNameFieldComboBox.currentField():
168-
QMessageBox.warning(self, "Missing Input", "Please select the unit name field.")
169-
return
170-
171-
if not self.stratUnitFieldComboBox.currentField():
172-
QMessageBox.warning(
173-
self, "Missing Input", "Please select the stratigraphic column unit name field."
155+
geology_layer = self.geologyLayerComboBox.currentLayer()
156+
unit_name_field = self.unitNameFieldComboBox.currentField()
157+
stratigraphic_order = self.data_manager.stratigraphic_order = (
158+
self.data_manager.get_stratigraphic_unit_names() if self.data_manager else []
159+
)
160+
paint_stratigraphic_order(
161+
geology_layer, stratigraphic_order, unit_name_field, debug_manager=self._debug
162+
)
163+
164+
# If requested, duplicate layer and apply style using selected colour ramp
165+
try:
166+
duplicate = (
167+
getattr(self, 'duplicateLayerCheckBox', None)
168+
and self.duplicateLayerCheckBox.isChecked()
174169
)
175-
return
170+
except Exception:
171+
duplicate = False
176172

177-
# Run the processing algorithm
178-
try:
179-
params = {
180-
'INPUT_POLYGONS': self.geologyLayerComboBox.currentLayer(),
181-
'UNIT_NAME_FIELD': self.unitNameFieldComboBox.currentField(),
182-
'INPUT_STRAT_COLUMN': self.stratColumnLayerComboBox.currentLayer(),
183-
'STRAT_UNIT_NAME_FIELD': self.stratUnitFieldComboBox.currentField(),
184-
'STRAT_THICKNESS_FIELD': self.stratThicknessFieldComboBox.currentField() or '',
185-
'PAINT_MODE': self.paintModeComboBox.currentIndex(),
186-
'OUTPUT': 'TEMPORARY_OUTPUT',
187-
}
173+
if duplicate:
174+
# Get chosen ramp name
175+
try:
176+
ramp_name = self.colorRampComboBox.currentText()
177+
except Exception:
178+
ramp_name = None
188179

189-
if self._debug and self._debug.is_debug():
190-
try:
191-
import json
192-
193-
params_json = json.dumps(
194-
self._serialize_params_for_logging(params, "paint_strat_order"),
195-
indent=2,
196-
).encode("utf-8")
197-
self._debug.save_debug_file("paint_strat_order_params.json", params_json)
198-
except Exception as err:
199-
self._debug.plugin.log(
200-
message=f"[map2loop] Failed to save paint strat order params: {err}",
201-
log_level=2,
202-
)
180+
# Step 1: create a memory copy of the geology layer and copy attributes/geometry
181+
try:
182+
from PyQt5.QtCore import QVariant
183+
from qgis.core import (
184+
QgsFeature,
185+
QgsField,
186+
QgsGraduatedSymbolRenderer,
187+
QgsProject,
188+
QgsRendererRange,
189+
QgsStyle,
190+
QgsSymbol,
191+
QgsVectorLayer,
192+
QgsWkbTypes,
193+
)
203194

204-
result = processing.run("plugin_map2loop:paint_stratigraphic_order", params)
195+
geom_type = QgsWkbTypes.displayString(geology_layer.wkbType())
196+
crs_auth = geology_layer.crs().authid() if hasattr(geology_layer, 'crs') else None
197+
uri = f"{geom_type}?crs={crs_auth}" if crs_auth else f"{geom_type}"
198+
mem_layer = QgsVectorLayer(uri, f"{geology_layer.name()}_strat", "memory")
199+
200+
mem_dp = mem_layer.dataProvider()
201+
mem_dp.addAttributes(list(geology_layer.fields()))
202+
mem_layer.updateFields()
203+
204+
# copy each feature and its attributes explicitly
205+
src_field_names = [f.name() for f in geology_layer.fields()]
206+
new_feats = []
207+
for src_feat in geology_layer.getFeatures():
208+
nf = QgsFeature()
209+
nf.setGeometry(src_feat.geometry())
210+
nf.setFields(mem_layer.fields())
211+
attrs = []
212+
for f in mem_layer.fields():
213+
fname = f.name()
214+
if fname in src_field_names:
215+
try:
216+
attrs.append(src_feat[fname])
217+
except Exception:
218+
attrs.append(None)
219+
else:
220+
attrs.append(None)
221+
nf.setAttributes(attrs)
222+
new_feats.append(nf)
223+
mem_dp.addFeatures(new_feats)
224+
mem_layer.updateExtents()
225+
QgsProject.instance().addMapLayer(mem_layer)
226+
except Exception as e:
227+
QMessageBox.warning(self, 'Duplicate Layer', f'Failed to create copy: {e}')
228+
return
229+
230+
# Step 2: ensure 'strat_order' exists on memory layer and populate numeric values by matching geometries
231+
field_name = 'strat_order'
232+
try:
233+
if field_name not in [f.name() for f in mem_layer.fields()]:
234+
mem_layer.startEditing()
235+
mem_dp.addAttributes([QgsField(field_name, QVariant.Int)])
236+
mem_layer.updateFields()
237+
mem_layer.commitChanges()
238+
239+
# build mapping from geometry WKB -> numeric strat value from original layer
240+
geom_to_val = {}
241+
for of in geology_layer.getFeatures():
242+
try:
243+
raw = of[field_name]
244+
except Exception:
245+
raw = None
246+
if raw is None:
247+
continue
248+
try:
249+
val = int(raw)
250+
except Exception:
251+
try:
252+
val = int(float(raw))
253+
except Exception:
254+
continue
255+
try:
256+
geom_to_val[of.geometry().asWkb()] = val
257+
except Exception:
258+
# fallback to WKT if asWkb unavailable
259+
try:
260+
geom_to_val[of.geometry().asWkt()] = val
261+
except Exception:
262+
continue
263+
264+
if geom_to_val:
265+
mem_layer.startEditing()
266+
strat_idx = mem_layer.fields().indexFromName(field_name)
267+
for mf in mem_layer.getFeatures():
268+
try:
269+
key = mf.geometry().asWkb()
270+
except Exception:
271+
try:
272+
key = mf.geometry().asWkt()
273+
except Exception:
274+
key = None
275+
if key is None:
276+
continue
277+
val = geom_to_val.get(key, None)
278+
if val is not None:
279+
mem_layer.changeAttributeValue(mf.id(), strat_idx, int(val))
280+
mem_layer.commitChanges()
281+
except Exception:
282+
# continue; styling will fallback if numeric data missing
283+
pass
205284

206-
if result and 'OUTPUT' in result:
207-
output_layer = result['OUTPUT']
208-
if output_layer:
209-
QgsProject.instance().addMapLayer(output_layer)
210-
211-
field_name = (
212-
'strat_order' if self.paintModeComboBox.currentIndex() == 0
213-
else 'cum_thickness'
214-
)
215-
285+
# Step 3: build graduated renderer using explicit ranges per unique strat value
286+
try:
287+
vals = set()
288+
for f in mem_layer.getFeatures():
289+
try:
290+
v = f[field_name]
291+
except Exception:
292+
v = None
293+
if v is None:
294+
continue
295+
try:
296+
vals.add(float(v))
297+
except Exception:
298+
continue
299+
unique_vals = sorted(vals)
300+
301+
if not unique_vals:
216302
QMessageBox.information(
217303
self,
218-
"Success",
219-
f"Stratigraphic order painted successfully!\n"
220-
f"Output layer added with '{field_name}' field.",
304+
'Styling',
305+
"No 'strat_order' values found on duplicated layer; leaving default styling.",
221306
)
222307
else:
223-
QMessageBox.warning(self, "Warning", "No output layer was generated.")
224-
else:
225-
QMessageBox.warning(self, "Warning", "Algorithm did not produce expected output.")
226-
227-
except Exception as e:
228-
if self._debug:
229-
self._debug.plugin.log(
230-
message=f"[map2loop] Paint stratigraphic order failed: {e}",
231-
log_level=2,
308+
from qgis.core import QgsRendererRange
309+
310+
ramp = QgsStyle().defaultStyle().colorRamp(ramp_name) if ramp_name else None
311+
ranges = []
312+
n = len(unique_vals)
313+
for i, v in enumerate(unique_vals):
314+
lower = v - 0.5
315+
upper = v + 0.5
316+
symbol = QgsSymbol.defaultSymbol(mem_layer.geometryType())
317+
if ramp:
318+
try:
319+
color = ramp.color(i / (n - 1) if n > 1 else 0)
320+
symbol.setColor(color)
321+
except Exception:
322+
pass
323+
label = str(int(v)) if float(v).is_integer() else str(v)
324+
ranges.append(QgsRendererRange(lower, upper, symbol, label))
325+
renderer = QgsGraduatedSymbolRenderer(field_name, ranges)
326+
mem_layer.setRenderer(renderer)
327+
mem_layer.triggerRepaint()
328+
except Exception as e:
329+
QMessageBox.warning(
330+
self, 'Duplicate Layer', f'Failed to apply graduated styling: {e}'
232331
)
233-
if PlgOptionsManager.get_debug_mode():
234-
raise e
235-
QMessageBox.critical(self, "Error", f"An error occurred: {str(e)}")
236332

237-
def get_parameters(self):
238-
"""Get current widget parameters.
239-
240-
Returns
241-
-------
242-
dict
243-
Dictionary of current widget parameters.
244-
"""
245-
return {
246-
'geology_layer': self.geologyLayerComboBox.currentLayer(),
247-
'unit_name_field': self.unitNameFieldComboBox.currentField(),
248-
'strat_column_layer': self.stratColumnLayerComboBox.currentLayer(),
249-
'strat_unit_field': self.stratUnitFieldComboBox.currentField(),
250-
'strat_thickness_field': self.stratThicknessFieldComboBox.currentField(),
251-
'paint_mode': self.paintModeComboBox.currentIndex(),
252-
}
253-
254-
def set_parameters(self, params):
255-
"""Set widget parameters.
256-
257-
Parameters
258-
----------
259-
params : dict
260-
Dictionary of parameters to set.
261-
"""
262-
if 'geology_layer' in params and params['geology_layer']:
263-
self.geologyLayerComboBox.setLayer(params['geology_layer'])
264-
if 'unit_name_field' in params and params['unit_name_field']:
265-
self.unitNameFieldComboBox.setField(params['unit_name_field'])
266-
if 'strat_column_layer' in params and params['strat_column_layer']:
267-
self.stratColumnLayerComboBox.setLayer(params['strat_column_layer'])
268-
if 'strat_unit_field' in params and params['strat_unit_field']:
269-
self.stratUnitFieldComboBox.setField(params['strat_unit_field'])
270-
if 'strat_thickness_field' in params and params['strat_thickness_field']:
271-
self.stratThicknessFieldComboBox.setField(params['strat_thickness_field'])
272-
if 'paint_mode' in params:
273-
self.paintModeComboBox.setCurrentIndex(params['paint_mode'])
333+
# QMessageBox.information(
334+
# self,
335+
# "Paint Stratigraphic Order",
336+
# "Stratigraphic order has been painted onto the geology layer.",
337+
# )

0 commit comments

Comments
 (0)