88
99from loopstructural .toolbelt .preferences import PlgOptionsManager
1010
11+ from ...main .m2l_api import paint_stratigraphic_order
12+
1113
1214class 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