1717# You should have received a copy of the GNU General Public License
1818# along with GemeindescanExporter. If not, see <https://www.gnu.org/licenses/>.
1919
20- from typing import Optional , Dict
20+ from typing import Optional , Dict , List
2121
2222from PyQt5 .QtCore import QVariant
2323from qgis .core import (QgsVectorLayer , QgsFields , QgsField , QgsFeatureSink , QgsFillSymbol , QgsLineSymbol ,
2424 QgsFeature , QgsProcessingFeedback , QgsRectangle , QgsSpatialIndex , QgsFeatureRequest ,
25- QgsMarkerSymbol , QgsSymbol , QgsSymbolLayer )
25+ QgsMarkerSymbol , QgsSymbol , QgsSymbolLayer , QgsPropertyCollection , QgsProperty )
2626
27+ from ..definitions .style import Style , PointStyle
2728from ..definitions .symbols import SymbolLayerType , SymbolType
29+ from ..definitions .types import StyleType
2830from ..model .snapshot import Legend
31+ from ..qgis_plugin_tools .tools .exceptions import QgsPluginNotImplementedException
2932from ..qgis_plugin_tools .tools .i18n import tr
3033
3134
3235class StylesToAttributes :
33- field_mapper = {"fill" : "fillColor" , "fill-opacity" : "fillOpacity" , "stroke" : "strokeColor" ,
34- "stroke-opacity" : "strokeOpacity" , "stroke-width" : "strokeWidth" , "type" : "shape" }
35-
36- DEFAULT_TEMPLATE = {
37- "fill" : "#000000" ,
38- "fill-opacity" : 1.0 ,
39- "stroke" : "#ffffff" ,
40- "stroke-opacity" : 1.0 ,
41- "stroke-width" : 1.0
42- }
4336
4437 def __init__ (self , layer : QgsVectorLayer , layer_name : str , feedback : QgsProcessingFeedback ,
45- field_template : Optional = None ,
4638 primary_layer : bool = False ):
4739 self .layer = layer
4840 self .layer_name = layer_name
@@ -51,6 +43,9 @@ def __init__(self, layer: QgsVectorLayer, layer_name: str, feedback: QgsProcessi
5143 self .renderer = self .layer .renderer ()
5244 self .symbol_type : SymbolType = SymbolType [self .renderer .type ()]
5345
46+ self .style_type : StyleType = StyleType .from_layer (layer )
47+ self .field_template = self .style_type .get_style ().to_dict ()
48+
5449 if self .symbol_type in [SymbolType .categorizedSymbol , SymbolType .graduatedSymbol ]:
5550 self .mapped_col = self .renderer .classAttribute ()
5651 else :
@@ -59,9 +54,6 @@ def __init__(self, layer: QgsVectorLayer, layer_name: str, feedback: QgsProcessi
5954 self .symbols = {}
6055 self .primary_layer = primary_layer
6156
62- field_template = field_template if field_template is not None else StylesToAttributes .DEFAULT_TEMPLATE
63- self .field_template = field_template .copy ()
64-
6557 self .fields : QgsFields = self ._generate_fields ()
6658 self .legend = {}
6759
@@ -75,61 +67,80 @@ def _rgb_extract(prop):
7567 def get_legend (self ) -> Dict :
7668 return {label : legend .to_dict () for label , legend in self .legend .items ()}
7769
70+ def get_symbols (self ) -> Dict :
71+ return {key : {** val , 'style' : val ['style' ].to_dict ()} for key , val in self .symbols .items ()}
72+
7873 def extract_styles_to_layer (self , sink : QgsFeatureSink , extent : Optional [QgsRectangle ] = None ):
7974 try :
8075 self ._update_symbols ()
81- self ._update_legend ()
8276 self ._copy_fields (sink , extent )
77+ self ._update_legend ()
8378 except Exception as e :
8479 self .feedback .reportError (tr ('Error occurred: {}' , e ), True )
8580 self .feedback .cancel ()
8681
87- def _get_style (self , symbol : QgsSymbol ):
82+ def _get_style (self , symbol : QgsSymbol ) -> Style :
8883 self .feedback .pushDebugInfo (str (type (symbol )))
8984
90- style = self .field_template .copy ()
85+ symbol_opacity : float = symbol .opacity ()
86+
9187 symbol_layer : QgsSymbolLayer = symbol .symbolLayers ()[0 ]
9288 if symbol_layer .subSymbol () is not None :
9389 return self ._get_style (symbol_layer .subSymbol ())
9490
91+ style : Style = self .style_type .get_style ()
9592 sym_type = SymbolLayerType [symbol_layer .layerType ()]
9693 sym = symbol_layer .properties ()
94+
95+ # Add data defined properties for the style
96+ if symbol_layer .hasDataDefinedProperties ():
97+ data_defined_props : QgsPropertyCollection = symbol_layer .dataDefinedProperties ()
98+ for key in data_defined_props .propertyKeys ():
99+ prop : QgsProperty = data_defined_props .property (key )
100+ if prop .field () != '' :
101+ style .add_data_defined_expression (prop .field (), prop .asExpression ())
102+
97103 if isinstance (symbol , QgsFillSymbol ):
98104 if sym_type == SymbolLayerType .SimpleLine :
99- style [ " type" ] = "line"
100- style [ " fill" ] = "#000000"
101- style [ "fill-opacity" ] = 0
102- style [ " stroke" ] = self ._rgb_extract (sym ['outline_color' ])[0 ]
103- style [ "stroke-opacity" ] = self ._rgb_extract (sym ['outline_color' ])[1 ]
104- style [ "stroke-width" ] = sym ['outline_width' ]
105+ style . type = "line"
106+ style . fill = "#000000"
107+ style . fill_opacity = 0
108+ style . stroke = self ._rgb_extract (sym ['outline_color' ])[0 ]
109+ style . stroke_opacity = symbol_opacity * self ._rgb_extract (sym ['outline_color' ])[1 ]
110+ style . stroke_width = float ( sym ['outline_width' ])
105111 if sym_type in [SymbolLayerType .CentroidFill , SymbolLayerType .SimpleFill ]:
106112 if sym_type == SymbolLayerType .CentroidFill :
107- style [ " type" ] = "circle"
113+ style . type = "circle"
108114 else :
109- style [ " type" ] = "rectangle"
110- style [ " fill" ] = self ._rgb_extract (sym ['color' ])[0 ]
111- style [ "fill-opacity" ] = self ._rgb_extract (sym ['color' ])[1 ]
112- style [ " stroke" ] = self ._rgb_extract (sym ['outline_color' ])[0 ]
113- style [ "stroke-opacity" ] = self ._rgb_extract (sym ['outline_color' ])[1 ]
114- style [ "stroke-width" ] = sym ['outline_width' ]
115+ style . type = "rectangle"
116+ style . fill = self ._rgb_extract (sym ['color' ])[0 ]
117+ style . fill_opacity = symbol_opacity * self ._rgb_extract (sym ['color' ])[1 ]
118+ style . stroke = self ._rgb_extract (sym ['outline_color' ])[0 ]
119+ style . stroke_opacity = symbol_opacity * self ._rgb_extract (sym ['outline_color' ])[1 ]
120+ style . stroke_width = float ( sym ['outline_width' ])
115121
116122 elif isinstance (symbol , QgsLineSymbol ):
117123 if sym_type == SymbolLayerType .SimpleLine :
118124 self .feedback .pushDebugInfo (symbol_layer .properties ())
119- style [ " type" ] = "line"
120- style [ " fill" ] = "transparent"
121- style [ "fill-opacity" ] = 0
122- style [ " stroke" ] = self ._rgb_extract (sym ['line_color' ])[0 ]
123- style [ "stroke-opacity" ] = self ._rgb_extract (sym ['line_color' ])[1 ]
124- style [ "stroke-width" ] = sym ['line_width' ]
125+ style . type = "line"
126+ style . fill = "transparent"
127+ style . fill_opacity = 0
128+ style . stroke = self ._rgb_extract (sym ['line_color' ])[0 ]
129+ style . stroke_opacity = symbol_opacity * self ._rgb_extract (sym ['line_color' ])[1 ]
130+ style . stroke_width = float ( sym ['line_width' ])
125131 elif isinstance (symbol , QgsMarkerSymbol ):
126132 if sym_type == SymbolLayerType .SimpleMarker :
127- style ["type" ] = "circle"
128- style ["fill" ] = self ._rgb_extract (sym ['color' ])[0 ]
129- style ["fill-opacity" ] = self ._rgb_extract (sym ['color' ])[1 ]
130- style ["stroke" ] = self ._rgb_extract (sym ['outline_color' ])[0 ]
131- style ["stroke-opacity" ] = self ._rgb_extract (sym ['outline_color' ])[1 ]
132- style ["stroke-width" ] = sym ['outline_width' ]
133+ style : PointStyle
134+ style .type = "circle"
135+ style .fill = self ._rgb_extract (sym ['color' ])[0 ]
136+ style .fill_opacity = symbol_opacity * self ._rgb_extract (sym ['color' ])[1 ]
137+ style .has_fill = style .fill_opacity > 0.0
138+ style .stroke = self ._rgb_extract (sym ['outline_color' ])[0 ]
139+ style .stroke_opacity = symbol_opacity * self ._rgb_extract (sym ['outline_color' ])[1 ]
140+ style .stroke_width = float (sym ['outline_width' ])
141+ style .has_stroke = style .stroke_opacity > 0.0
142+ style .radius = sym ['size' ]
143+
133144 else :
134145 raise ValueError (f"Unkown symbol type: { symbol_layer .layerType ()} " )
135146 return style
@@ -171,13 +182,12 @@ def _copy_fields(self, sink: QgsFeatureSink, extent: Optional[QgsRectangle] = No
171182 sink .addFeature (f , QgsFeatureSink .FastInsert )
172183 else :
173184 feat = QgsFeature ()
174- style_attributes = self ._get_style_for_feature (f )
175-
176- attributes = f .attributes () + [style_attributes [key ] for key in
177- sorted (style_attributes .keys ())]
185+ attributes = self ._get_attributes_for_feature (f )
178186 feat .setAttributes (attributes )
179187 feat .setGeometry (f .geometry ())
180- sink .addFeature (feat , QgsFeatureSink .FastInsert )
188+ succeeded = sink .addFeature (feat , QgsFeatureSink .FastInsert )
189+ if not succeeded :
190+ raise ValueError (tr ('Could not add feature to target layer. Attributes: {}' , attributes ))
181191 self .feedback .setProgress (int (current * total ))
182192
183193 def _generate_fields (self ) -> QgsFields :
@@ -186,15 +196,19 @@ def _generate_fields(self) -> QgsFields:
186196 if field_template_name not in self .layer .fields ().names ():
187197 if isinstance (field_template_value , str ):
188198 field = QgsField (field_template_name , QVariant .String )
189- fields .append (field )
190199 elif isinstance (field_template_value , float ):
191200 field = QgsField (field_template_name , QVariant .Double )
192- fields .append (field )
201+ elif isinstance (field_template_value , bool ):
202+ field = QgsField (field_template_name , QVariant .Bool )
203+ else :
204+ raise QgsPluginNotImplementedException (
205+ tr ('Field type not implemented: {}' , type (field_template_value )))
206+ fields .append (field )
193207
194208 return fields
195209
196- def _get_style_for_feature (self , feature : QgsFeature ):
197- ret_val = {}
210+ def _get_attributes_for_feature (self , feature : QgsFeature ) -> List [ any ] :
211+ attributes = {i : feature [ field . name ()] for i , field in enumerate ( feature . fields (). toList ()) }
198212 if self .symbol_type == SymbolType .graduatedSymbol :
199213 feature_value = feature [self .mapped_col ]
200214 matched = None
@@ -205,7 +219,10 @@ def _get_style_for_feature(self, feature: QgsFeature):
205219 break
206220 if matched is not None :
207221 for field_name in self .field_template .keys ():
208- ret_val [self .fields .names ().index (field_name )] = self .symbols [matched ]['style' ][field_name ]
222+ style : Style = self .symbols [matched ]['style' ]
223+ style .evaluate_data_defined_expressions (feature )
224+ attributes [self .fields .names ().index (field_name )] = style .to_dict ()[
225+ field_name ]
209226
210227 elif self .symbol_type == SymbolType .categorizedSymbol :
211228 feature_value = feature [self .mapped_col ]
@@ -216,22 +233,26 @@ def _get_style_for_feature(self, feature: QgsFeature):
216233 matched = index
217234 if matched is not None :
218235 for field_name in self .field_template .keys ():
219- ret_val [self .fields .names ().index (field_name )] = self .symbols [matched ]['style' ][field_name ]
236+ style = self .symbols [matched ]['style' ]
237+ style .evaluate_data_defined_expressions (feature )
238+ attributes [self .fields .names ().index (field_name )] = style .to_dict ()[
239+ field_name ]
220240
221241 elif self .symbol_type == SymbolType .singleSymbol :
222242 for field_name in self .field_template .keys ():
223- ret_val [self .fields .names ().index (field_name )] = self .symbols [0 ]['style' ][field_name ]
243+ style = self .symbols [0 ]['style' ]
244+ style .evaluate_data_defined_expressions (feature )
245+ attributes [self .fields .names ().index (field_name )] = style .to_dict ()[field_name ]
224246
225247 # TODO: Add more
226248
227- return ret_val
249+ return [ attributes [ key ] for key in sorted ( attributes . keys ())]
228250
229251 def _update_legend (self ):
230252 legend = {}
231253 i = 0
232254 for index , item in self .symbols .items ():
233- legend_style = item ["style" ].copy ()
234- legend_style = {self .field_mapper [key ]: value for key , value in legend_style .items ()}
255+ legend_style = item ["style" ].legend_style
235256 legend_style ['size' ] = 1
236257 legend_style ["primary" ] = self .primary_layer and (i == 0 or i == (len (self .symbols .items ()) - 1 ))
237258 legend_style ["label" ] = item ["label" ]
0 commit comments