1010"""
1111# Python imports
1212from typing import Any , Optional
13- from qgis .PyQt .QtCore import QMetaType
13+ from qgis .PyQt .QtCore import QVariant
14+ from osgeo import gdal
15+ import pandas as pd
1416
1517# QGIS imports
1618from qgis .core import (
2224 QgsProcessingFeedback ,
2325 QgsProcessingParameterFeatureSink ,
2426 QgsProcessingParameterFeatureSource ,
25- QgsProcessingParameterString ,
27+ QgsProcessingParameterRasterLayer ,
28+ QgsProcessingParameterEnum ,
2629 QgsProcessingParameterNumber ,
30+ QgsFields ,
2731 QgsField ,
2832 QgsFeature ,
2933 QgsGeometry ,
3034 QgsPointXY ,
31- QgsVectorLayer
35+ QgsVectorLayer ,
36+ QgsWkbTypes ,
37+ QgsCoordinateReferenceSystem
3238)
3339# Internal imports
3440from ...main .vectorLayerWrapper import qgsLayerToGeoDataFrame
@@ -68,17 +74,20 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None:
6874
6975
7076 self .addParameter (
71- QgsProcessingParameterString (
77+ QgsProcessingParameterEnum (
7278 self .INPUT_SAMPLER_TYPE ,
7379 "SAMPLER_TYPE" ,
80+ ["Decimator" , "Spacing" ],
81+ defaultValue = 0
7482 )
7583 )
7684
7785 self .addParameter (
78- QgsProcessingParameterFeatureSource (
86+ QgsProcessingParameterRasterLayer (
7987 self .INPUT_DTM ,
8088 "DTM" ,
8189 [QgsProcessing .TypeRaster ],
90+ optional = True ,
8291 )
8392 )
8493
@@ -104,6 +113,8 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None:
104113 QgsProcessingParameterNumber (
105114 self .INPUT_DECIMATION ,
106115 "DECIMATION" ,
116+ QgsProcessingParameterNumber .Integer ,
117+ defaultValue = 1 ,
107118 optional = True ,
108119 )
109120 )
@@ -112,14 +123,16 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None:
112123 QgsProcessingParameterNumber (
113124 self .INPUT_SPACING ,
114125 "SPACING" ,
126+ QgsProcessingParameterNumber .Double ,
127+ defaultValue = 200.0 ,
115128 optional = True ,
116129 )
117130 )
118131
119132 self .addParameter (
120133 QgsProcessingParameterFeatureSink (
121134 self .OUTPUT ,
122- "Sampled Contacts " ,
135+ "Sampled Points " ,
123136 )
124137 )
125138
@@ -130,60 +143,77 @@ def processAlgorithm(
130143 feedback : QgsProcessingFeedback ,
131144 ) -> dict [str , Any ]:
132145
133- dtm = self .parameterAsSource (parameters , self .INPUT_DTM , context )
134- geology = self .parameterAsSource (parameters , self .INPUT_GEOLOGY , context )
135- spatial_data = self .parameterAsSource (parameters , self .INPUT_SPATIAL_DATA , context )
136- decimation = self .parameterAsSource (parameters , self .INPUT_DECIMATION , context )
137- spacing = self .parameterAsSource (parameters , self .INPUT_SPACING , context )
138- sampler_type = self .parameterAsString (parameters , self .INPUT_SAMPLER_TYPE , context )
146+ dtm = self .parameterAsRasterLayer (parameters , self .INPUT_DTM , context )
147+ geology = self .parameterAsVectorLayer (parameters , self .INPUT_GEOLOGY , context )
148+ spatial_data = self .parameterAsVectorLayer (parameters , self .INPUT_SPATIAL_DATA , context )
149+ decimation = self .parameterAsInt (parameters , self .INPUT_DECIMATION , context )
150+ spacing = self .parameterAsDouble (parameters , self .INPUT_SPACING , context )
151+ sampler_type_index = self .parameterAsEnum (parameters , self .INPUT_SAMPLER_TYPE , context )
152+ sampler_type = ["Decimator" , "Spacing" ][sampler_type_index ]
153+
154+ if spatial_data is None :
155+ raise QgsProcessingException ("Spatial data is required" )
156+
157+ if sampler_type == "Decimator" and geology is None :
158+ raise QgsProcessingException ("Geology is required" )
139159
140160 # Convert geology layers to GeoDataFrames
141161 geology = qgsLayerToGeoDataFrame (geology )
142- spatial_data = qgsLayerToGeoDataFrame (spatial_data )
162+ spatial_data_gdf = qgsLayerToGeoDataFrame (spatial_data )
163+ dtm_gdal = gdal .Open (dtm .source ()) if dtm is not None and dtm .isValid () else None
143164
144- if sampler_type == "decimator " :
165+ if sampler_type == "Decimator " :
145166 feedback .pushInfo ("Sampling..." )
146- sampler = SamplerDecimator (decimation = decimation , dtm_data = dtm , geology_data = geology , feedback = feedback )
147- samples = sampler .sample (spatial_data )
167+ sampler = SamplerDecimator (decimation = decimation , dtm_data = dtm_gdal , geology_data = geology )
168+ samples = sampler .sample (spatial_data_gdf )
148169
149- if sampler_type == "spacing " :
170+ if sampler_type == "Spacing " :
150171 feedback .pushInfo ("Sampling..." )
151- sampler = SamplerSpacing (spacing = spacing , dtm_data = dtm , geology_data = geology , feedback = feedback )
152- samples = sampler .sample (spatial_data )
153-
154-
155- # create layer
156- vector_layer = QgsVectorLayer ("Point" , "sampled_points" , "memory" )
157- provider = vector_layer .dataProvider ()
158-
159- # add fields
160- provider .addAttributes ([QgsField ("ID" , QMetaType .Type .QString ),
161- QgsField ("X" , QMetaType .Type .Float ),
162- QgsField ("Y" , QMetaType .Type .Float ),
163- QgsField ("Z" , QMetaType .Type .Float ),
164- QgsField ("featureId" , QMetaType .Type .QString )
165- ])
166- vector_layer .updateFields () # tell the vector layer to fetch changes from the provider
167-
168- # add a feature
169- for i in range (len (samples )):
170- feature = QgsFeature ()
171- feature .setGeometry (QgsGeometry .fromPointXY (QgsPointXY (samples .X [i ], samples .Y [i ], samples .Z [i ])))
172- feature .setAttributes ([samples .ID [i ], samples .X [i ], samples .Y [i ], samples .Z [i ], samples .featureId [i ]])
173- provider .addFeatures ([feature ])
174-
175- # update layer's extent when new features have been added
176- # because change of extent in provider is not propagated to the layer
177- vector_layer .updateExtents ()
178- # --- create sink
172+ sampler = SamplerSpacing (spacing = spacing , dtm_data = dtm_gdal , geology_data = geology )
173+ samples = sampler .sample (spatial_data_gdf )
174+
175+ fields = QgsFields ()
176+ fields .append (QgsField ("ID" , QVariant .String ))
177+ fields .append (QgsField ("X" , QVariant .Double ))
178+ fields .append (QgsField ("Y" , QVariant .Double ))
179+ fields .append (QgsField ("Z" , QVariant .Double ))
180+ fields .append (QgsField ("featureId" , QVariant .String ))
181+
182+ crs = None
183+ if spatial_data_gdf is not None and spatial_data_gdf .crs is not None :
184+ crs = QgsCoordinateReferenceSystem .fromWkt (spatial_data_gdf .crs .to_wkt ())
185+
179186 sink , dest_id = self .parameterAsSink (
180187 parameters ,
181188 self .OUTPUT ,
182189 context ,
183- vector_layer .fields (),
184- QgsGeometry .Type .Point ,
185- spatial_data .crs ,
186- )
190+ fields ,
191+ QgsWkbTypes .PointZ if 'Z' in (samples .columns if samples is not None else []) else QgsWkbTypes .Point ,
192+ crs
193+ )
194+
195+ if samples is not None and not samples .empty :
196+ for _index , row in samples .iterrows ():
197+ feature = QgsFeature (fields )
198+
199+ # decimator has z values
200+ if 'Z' in samples .columns and pd .notna (row .get ('Z' )):
201+ wkt = f"POINT Z ({ row ['X' ]} { row ['Y' ]} { row ['Z' ]} )"
202+ feature .setGeometry (QgsGeometry .fromWkt (wkt ))
203+ else :
204+ #spacing has no z values
205+ feature .setGeometry (QgsGeometry .fromPointXY (QgsPointXY (row ['X' ], row ['Y' ])))
206+
207+ feature .setAttributes ([
208+ str (row .get ('ID' , '' )),
209+ float (row .get ('X' , 0 )),
210+ float (row .get ('Y' , 0 )),
211+ float (row .get ('Z' , 0 )) if pd .notna (row .get ('Z' )) else 0.0 ,
212+ str (row .get ('featureId' , '' ))
213+ ])
214+
215+ sink .addFeature (feature )
216+
187217 return {self .OUTPUT : dest_id }
188218
189219 def createInstance (self ) -> QgsProcessingAlgorithm :
0 commit comments