diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml index 4543564..cf13a1b 100644 --- a/.github/workflows/tester.yml +++ b/.github/workflows/tester.yml @@ -2,16 +2,12 @@ name: "🎳 Tester" on: push: - branches: - - main paths: - '**.py' - .github/workflows/tester.yml - requirements/testing.txt pull_request: - branches: - - main paths: - '**.py' - .github/workflows/tester.yml @@ -45,55 +41,62 @@ jobs: - name: Run Unit tests run: pytest -p no:qgis tests/unit/ - # test-qgis: - # runs-on: ubuntu-latest - - # container: - # image: qgis/qgis:3.4 - # env: - # CI: true - # DISPLAY: ":1" - # MUTE_LOGS: true - # NO_MODALS: 1 - # PYTHONPATH: "/usr/share/qgis/python/plugins:/usr/share/qgis/python:." - # QT_QPA_PLATFORM: "offscreen" - # WITH_PYTHON_PEP: false - # # be careful, things have changed since QGIS 3.40. So if you are using this setup - # # with a QGIS version older than 3.40, you may need to change the way you set up the container - # volumes: - # # Mount the X11 socket to allow GUI applications to run - # - /tmp/.X11-unix:/tmp/.X11-unix - # # Mount the workspace directory to the container - # - ${{ github.workspace }}:/home/root/ - - # steps: - # - name: Get source code - # uses: actions/checkout@v4 - - # - name: Print QGIS version - # run: qgis --version - - # # Uncomment if you need to run a script to set up the plugin in QGIS docker image < 3.40 - # # - name: Setup plugin - # # run: qgis_setup.sh ${{ env.PROJECT_FOLDER }} - - # - name: Install Python requirements - # run: | - # apt update && apt install -y python3-pip python3-venv pipx - # # Create a virtual environment - # cd /home/root/ - # pipx run qgis-venv-creator --venv-name ".venv" - # # Activate the virtual environment - # . .venv/bin/activate - # # Install the requirements - # python3 -m pip install -U -r requirements/testing.txt - - # - name: Run Unit tests - # run: | - # cd /home/root/ - # # Activate the virtual environment - # . .venv/bin/activate - # # Run the tests - # # xvfb-run is used to run the tests in a virtual framebuffer - # # This is necessary because QGIS requires a display to run - # xvfb-run python3 -m pytest tests/qgis --junitxml=junit/test-results-qgis.xml --cov-report=xml:coverage-reports/coverage-qgis.xml + test-qgis: + runs-on: ubuntu-latest + + container: + image: qgis/qgis:latest + env: + CI: true + DISPLAY: ":1" + MUTE_LOGS: true + NO_MODALS: 1 + PYTHONPATH: "/usr/share/qgis/python/plugins:/usr/share/qgis/python:." + QT_QPA_PLATFORM: "offscreen" + WITH_PYTHON_PEP: false + # be careful, things have changed since QGIS 3.40. So if you are using this setup + # with a QGIS version older than 3.40, you may need to change the way you set up the container + volumes: + # Mount the X11 socket to allow GUI applications to run + - /tmp/.X11-unix:/tmp/.X11-unix + # Mount the workspace directory to the container + - ${{ github.workspace }}:/home/root/ + + steps: + - name: Get source code + uses: actions/checkout@v4 + + - name: Print QGIS version + run: qgis --version + + # Uncomment if you need to run a script to set up the plugin in QGIS docker image < 3.40 + # - name: Setup plugin + # run: qgis_setup.sh ${{ env.PROJECT_FOLDER }} + + - name: Install Python requirements + run: | + apt update && apt install -y python3-pip python3-venv pipx + # Create a virtual environment + cd /home/root/ + pipx run qgis-venv-creator --venv-name ".venv" + # Activate the virtual environment + . .venv/bin/activate + # Install the requirements + python3 -m pip install -U -r requirements/testing.txt + python3 -m pip install git+https://github.com/Loop3D/map2loop.git@noelle/contact_extractor + + - name: verify input data + run: | + cd /home/root/ + . .venv/bin/activate + ls -la tests/qgis/input/ || echo "Input directory not found" + + - name: Run Unit tests + run: | + cd /home/root/ + # Activate the virtual environment + . .venv/bin/activate + # Run the tests + # xvfb-run is used to run the tests in a virtual framebuffer + # This is necessary because QGIS requires a display to run + xvfb-run python3 -m pytest tests/qgis --junitxml=junit/test-results-qgis.xml --cov-report=xml:coverage-reports/coverage-qgis.xml diff --git a/m2l/processing/algorithms/__init__.py b/m2l/processing/algorithms/__init__.py index e3201dc..f0aaedb 100644 --- a/m2l/processing/algorithms/__init__.py +++ b/m2l/processing/algorithms/__init__.py @@ -1,4 +1,4 @@ from .extract_basal_contacts import BasalContactsAlgorithm from .sorter import StratigraphySorterAlgorithm from .thickness_calculator import ThicknessCalculatorAlgorithm -from .sampler import SamplerAlgorithm \ No newline at end of file +from .sampler import SamplerAlgorithm diff --git a/m2l/processing/algorithms/sampler.py b/m2l/processing/algorithms/sampler.py index a4b61f6..b98b1ad 100644 --- a/m2l/processing/algorithms/sampler.py +++ b/m2l/processing/algorithms/sampler.py @@ -11,6 +11,8 @@ # Python imports from typing import Any, Optional from qgis.PyQt.QtCore import QMetaType +from osgeo import gdal +import pandas as pd # QGIS imports from qgis.core import ( @@ -22,13 +24,17 @@ QgsProcessingFeedback, QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSource, - QgsProcessingParameterString, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterEnum, QgsProcessingParameterNumber, + QgsFields, QgsField, QgsFeature, QgsGeometry, QgsPointXY, - QgsVectorLayer + QgsVectorLayer, + QgsWkbTypes, + QgsCoordinateReferenceSystem ) # Internal imports from ...main.vectorLayerWrapper import qgsLayerToGeoDataFrame @@ -68,17 +74,20 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None: self.addParameter( - QgsProcessingParameterString( + QgsProcessingParameterEnum( self.INPUT_SAMPLER_TYPE, "SAMPLER_TYPE", + ["Decimator", "Spacing"], + defaultValue=0 ) ) self.addParameter( - QgsProcessingParameterFeatureSource( + QgsProcessingParameterRasterLayer( self.INPUT_DTM, "DTM", [QgsProcessing.TypeRaster], + optional=True, ) ) @@ -104,6 +113,8 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None: QgsProcessingParameterNumber( self.INPUT_DECIMATION, "DECIMATION", + QgsProcessingParameterNumber.Integer, + defaultValue=1, optional=True, ) ) @@ -112,6 +123,8 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None: QgsProcessingParameterNumber( self.INPUT_SPACING, "SPACING", + QgsProcessingParameterNumber.Double, + defaultValue=200.0, optional=True, ) ) @@ -119,7 +132,7 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None: self.addParameter( QgsProcessingParameterFeatureSink( self.OUTPUT, - "Sampled Contacts", + "Sampled Points", ) ) @@ -130,60 +143,80 @@ def processAlgorithm( feedback: QgsProcessingFeedback, ) -> dict[str, Any]: - dtm = self.parameterAsSource(parameters, self.INPUT_DTM, context) - geology = self.parameterAsSource(parameters, self.INPUT_GEOLOGY, context) - spatial_data = self.parameterAsSource(parameters, self.INPUT_SPATIAL_DATA, context) - decimation = self.parameterAsSource(parameters, self.INPUT_DECIMATION, context) - spacing = self.parameterAsSource(parameters, self.INPUT_SPACING, context) - sampler_type = self.parameterAsString(parameters, self.INPUT_SAMPLER_TYPE, context) + dtm = self.parameterAsRasterLayer(parameters, self.INPUT_DTM, context) + geology = self.parameterAsVectorLayer(parameters, self.INPUT_GEOLOGY, context) + spatial_data = self.parameterAsVectorLayer(parameters, self.INPUT_SPATIAL_DATA, context) + decimation = self.parameterAsInt(parameters, self.INPUT_DECIMATION, context) + spacing = self.parameterAsDouble(parameters, self.INPUT_SPACING, context) + sampler_type_index = self.parameterAsEnum(parameters, self.INPUT_SAMPLER_TYPE, context) + sampler_type = ["Decimator", "Spacing"][sampler_type_index] + + if spatial_data is None: + raise QgsProcessingException("Spatial data is required") + + if sampler_type is "Decimator": + if geology is None: + raise QgsProcessingException("Geology is required") + if dtm is None: + raise QgsProcessingException("DTM is required") # Convert geology layers to GeoDataFrames geology = qgsLayerToGeoDataFrame(geology) - spatial_data = qgsLayerToGeoDataFrame(spatial_data) + spatial_data_gdf = qgsLayerToGeoDataFrame(spatial_data) + dtm_gdal = gdal.Open(dtm.source()) if dtm is not None and dtm.isValid() else None - if sampler_type == "decimator": + if sampler_type == "Decimator": feedback.pushInfo("Sampling...") - sampler = SamplerDecimator(decimation=decimation, dtm_data=dtm, geology_data=geology, feedback=feedback) - samples = sampler.sample(spatial_data) + sampler = SamplerDecimator(decimation=decimation, dtm_data=dtm_gdal, geology_data=geology) + samples = sampler.sample(spatial_data_gdf) - if sampler_type == "spacing": + if sampler_type == "Spacing": feedback.pushInfo("Sampling...") - sampler = SamplerSpacing(spacing=spacing, dtm_data=dtm, geology_data=geology, feedback=feedback) - samples = sampler.sample(spatial_data) - - - # create layer - vector_layer = QgsVectorLayer("Point", "sampled_points", "memory") - provider = vector_layer.dataProvider() - - # add fields - provider.addAttributes([QgsField("ID", QMetaType.Type.QString), - QgsField("X", QMetaType.Type.Float), - QgsField("Y", QMetaType.Type.Float), - QgsField("Z", QMetaType.Type.Float), - QgsField("featureId", QMetaType.Type.QString) - ]) - vector_layer.updateFields() # tell the vector layer to fetch changes from the provider - - # add a feature - for i in range(len(samples)): - feature = QgsFeature() - feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(samples.X[i], samples.Y[i], samples.Z[i]))) - feature.setAttributes([samples.ID[i], samples.X[i], samples.Y[i], samples.Z[i], samples.featureId[i]]) - provider.addFeatures([feature]) - - # update layer's extent when new features have been added - # because change of extent in provider is not propagated to the layer - vector_layer.updateExtents() - # --- create sink + sampler = SamplerSpacing(spacing=spacing, dtm_data=dtm_gdal, geology_data=geology) + samples = sampler.sample(spatial_data_gdf) + + fields = QgsFields() + fields.append(QgsField("ID", QMetaType.Type.QString)) + fields.append(QgsField("X", QMetaType.Type.Float)) + fields.append(QgsField("Y", QMetaType.Type.Float)) + fields.append(QgsField("Z", QMetaType.Type.Float)) + fields.append(QgsField("featureId", QMetaType.Type.QString)) + + crs = None + if spatial_data_gdf is not None and spatial_data_gdf.crs is not None: + crs = QgsCoordinateReferenceSystem.fromWkt(spatial_data_gdf.crs.to_wkt()) + sink, dest_id = self.parameterAsSink( parameters, self.OUTPUT, context, - vector_layer.fields(), - QgsGeometry.Type.Point, - spatial_data.crs, - ) + fields, + QgsWkbTypes.PointZ if 'Z' in (samples.columns if samples is not None else []) else QgsWkbTypes.Point, + crs + ) + + if samples is not None and not samples.empty: + for _index, row in samples.iterrows(): + feature = QgsFeature(fields) + + # decimator has z values + if 'Z' in samples.columns and pd.notna(row.get('Z')): + wkt = f"POINT Z ({row['X']} {row['Y']} {row['Z']})" + feature.setGeometry(QgsGeometry.fromWkt(wkt)) + else: + #spacing has no z values + feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(row['X'], row['Y']))) + + feature.setAttributes([ + str(row.get('ID', '')), + float(row.get('X', 0)), + float(row.get('Y', 0)), + float(row.get('Z', 0)) if pd.notna(row.get('Z')) else 0.0, + str(row.get('featureId', '')) + ]) + + sink.addFeature(feature) + return {self.OUTPUT: dest_id} def createInstance(self) -> QgsProcessingAlgorithm: diff --git a/m2l/processing/algorithms/sorter.py b/m2l/processing/algorithms/sorter.py index 215e79a..849a18a 100644 --- a/m2l/processing/algorithms/sorter.py +++ b/m2l/processing/algorithms/sorter.py @@ -92,7 +92,7 @@ def initAlgorithm(self, config: Optional[dict[str, Any]] = None) -> None: self.addParameter( QgsProcessingParameterFeatureSink( self.OUTPUT, - self.tr("Stratigraphic column"), + "Stratigraphic column", ) ) @@ -177,7 +177,7 @@ def build_input_frames(layer: QgsVectorLayer, feedback) -> tuple: (units_df, relationships_df, contacts_df, map_data) """ import pandas as pd - from m2l.map2loop.mapdata import MapData # adjust import path if needed + from map2loop.map2loop.mapdata import MapData # adjust import path if needed # Example: convert the geology layer to a very small units_df units_records = [] diff --git a/requirements/testing.txt b/requirements/testing.txt index 1940035..d238575 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -3,3 +3,5 @@ pytest-cov>=4 packaging>=23 +shapely +geopandas \ No newline at end of file diff --git a/tests/qgis/input/dtm_rp.tif b/tests/qgis/input/dtm_rp.tif new file mode 100644 index 0000000..a9ba843 Binary files /dev/null and b/tests/qgis/input/dtm_rp.tif differ diff --git a/tests/qgis/input/dtm_rp.tif.aux.xml b/tests/qgis/input/dtm_rp.tif.aux.xml new file mode 100644 index 0000000..923608b --- /dev/null +++ b/tests/qgis/input/dtm_rp.tif.aux.xml @@ -0,0 +1,11 @@ + + + + 1175 + 561.72162965205 + 297 + 107.52060579962 + 99.73 + + + diff --git a/tests/qgis/input/faults_clip.cpg b/tests/qgis/input/faults_clip.cpg new file mode 100644 index 0000000..cd89cb9 --- /dev/null +++ b/tests/qgis/input/faults_clip.cpg @@ -0,0 +1 @@ +ISO-8859-1 \ No newline at end of file diff --git a/tests/qgis/input/faults_clip.dbf b/tests/qgis/input/faults_clip.dbf new file mode 100644 index 0000000..35f7f0e Binary files /dev/null and b/tests/qgis/input/faults_clip.dbf differ diff --git a/tests/qgis/input/faults_clip.prj b/tests/qgis/input/faults_clip.prj new file mode 100644 index 0000000..51bb0e5 --- /dev/null +++ b/tests/qgis/input/faults_clip.prj @@ -0,0 +1 @@ +PROJCS["GDA94_MGA_zone_50",GEOGCS["GCS_GDA94",DATUM["D_Geocentric_Datum_of_Australia_1994",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",10000000.0],PARAMETER["Central_Meridian",117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/tests/qgis/input/faults_clip.shp b/tests/qgis/input/faults_clip.shp new file mode 100644 index 0000000..7a0e9a2 Binary files /dev/null and b/tests/qgis/input/faults_clip.shp differ diff --git a/tests/qgis/input/faults_clip.shx b/tests/qgis/input/faults_clip.shx new file mode 100644 index 0000000..f129118 Binary files /dev/null and b/tests/qgis/input/faults_clip.shx differ diff --git a/tests/qgis/input/folds_clip.cpg b/tests/qgis/input/folds_clip.cpg new file mode 100644 index 0000000..cd89cb9 --- /dev/null +++ b/tests/qgis/input/folds_clip.cpg @@ -0,0 +1 @@ +ISO-8859-1 \ No newline at end of file diff --git a/tests/qgis/input/folds_clip.dbf b/tests/qgis/input/folds_clip.dbf new file mode 100644 index 0000000..fc04d6c Binary files /dev/null and b/tests/qgis/input/folds_clip.dbf differ diff --git a/tests/qgis/input/folds_clip.prj b/tests/qgis/input/folds_clip.prj new file mode 100644 index 0000000..51bb0e5 --- /dev/null +++ b/tests/qgis/input/folds_clip.prj @@ -0,0 +1 @@ +PROJCS["GDA94_MGA_zone_50",GEOGCS["GCS_GDA94",DATUM["D_Geocentric_Datum_of_Australia_1994",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",10000000.0],PARAMETER["Central_Meridian",117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/tests/qgis/input/folds_clip.shp b/tests/qgis/input/folds_clip.shp new file mode 100644 index 0000000..509566c Binary files /dev/null and b/tests/qgis/input/folds_clip.shp differ diff --git a/tests/qgis/input/folds_clip.shx b/tests/qgis/input/folds_clip.shx new file mode 100644 index 0000000..981c6ce Binary files /dev/null and b/tests/qgis/input/folds_clip.shx differ diff --git a/tests/qgis/input/geol_clip_no_gaps.cpg b/tests/qgis/input/geol_clip_no_gaps.cpg new file mode 100644 index 0000000..cd89cb9 --- /dev/null +++ b/tests/qgis/input/geol_clip_no_gaps.cpg @@ -0,0 +1 @@ +ISO-8859-1 \ No newline at end of file diff --git a/tests/qgis/input/geol_clip_no_gaps.dbf b/tests/qgis/input/geol_clip_no_gaps.dbf new file mode 100644 index 0000000..832c68c Binary files /dev/null and b/tests/qgis/input/geol_clip_no_gaps.dbf differ diff --git a/tests/qgis/input/geol_clip_no_gaps.prj b/tests/qgis/input/geol_clip_no_gaps.prj new file mode 100644 index 0000000..51bb0e5 --- /dev/null +++ b/tests/qgis/input/geol_clip_no_gaps.prj @@ -0,0 +1 @@ +PROJCS["GDA94_MGA_zone_50",GEOGCS["GCS_GDA94",DATUM["D_Geocentric_Datum_of_Australia_1994",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",10000000.0],PARAMETER["Central_Meridian",117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/tests/qgis/input/geol_clip_no_gaps.shp b/tests/qgis/input/geol_clip_no_gaps.shp new file mode 100644 index 0000000..0d7d669 Binary files /dev/null and b/tests/qgis/input/geol_clip_no_gaps.shp differ diff --git a/tests/qgis/input/geol_clip_no_gaps.shx b/tests/qgis/input/geol_clip_no_gaps.shx new file mode 100644 index 0000000..78d33f6 Binary files /dev/null and b/tests/qgis/input/geol_clip_no_gaps.shx differ diff --git a/tests/qgis/input/structure_clip.cpg b/tests/qgis/input/structure_clip.cpg new file mode 100644 index 0000000..cd89cb9 --- /dev/null +++ b/tests/qgis/input/structure_clip.cpg @@ -0,0 +1 @@ +ISO-8859-1 \ No newline at end of file diff --git a/tests/qgis/input/structure_clip.dbf b/tests/qgis/input/structure_clip.dbf new file mode 100644 index 0000000..7f15994 Binary files /dev/null and b/tests/qgis/input/structure_clip.dbf differ diff --git a/tests/qgis/input/structure_clip.prj b/tests/qgis/input/structure_clip.prj new file mode 100644 index 0000000..51bb0e5 --- /dev/null +++ b/tests/qgis/input/structure_clip.prj @@ -0,0 +1 @@ +PROJCS["GDA94_MGA_zone_50",GEOGCS["GCS_GDA94",DATUM["D_Geocentric_Datum_of_Australia_1994",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",10000000.0],PARAMETER["Central_Meridian",117.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/tests/qgis/input/structure_clip.shp b/tests/qgis/input/structure_clip.shp new file mode 100644 index 0000000..60a7773 Binary files /dev/null and b/tests/qgis/input/structure_clip.shp differ diff --git a/tests/qgis/input/structure_clip.shx b/tests/qgis/input/structure_clip.shx new file mode 100644 index 0000000..cb743f8 Binary files /dev/null and b/tests/qgis/input/structure_clip.shx differ diff --git a/tests/qgis/test_sampler_decimator.py b/tests/qgis/test_sampler_decimator.py new file mode 100644 index 0000000..088fd29 --- /dev/null +++ b/tests/qgis/test_sampler_decimator.py @@ -0,0 +1,98 @@ +import unittest +from pathlib import Path +from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsProcessingContext, QgsProcessingFeedback, QgsMessageLog, Qgis,QgsApplication +from qgis.testing import start_app +from m2l.processing.algorithms.sampler import SamplerAlgorithm +from m2l.processing.provider import Map2LoopProvider + +class TestSamplerDecimator(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.qgs = start_app() + + cls.provider = Map2LoopProvider() + QgsApplication.processingRegistry().addProvider(cls.provider) + + def setUp(self): + self.test_dir = Path(__file__).parent + self.input_dir = self.test_dir / "input" + + self.geology_file = self.input_dir / "geol_clip_no_gaps.shp" + self.structure_file = self.input_dir / "structure_clip.shp" + self.dtm_file = self.input_dir / "dtm_rp.tif" + + self.assertTrue(self.geology_file.exists(), f"geology not found: {self.geology_file}") + self.assertTrue(self.structure_file.exists(), f"structure not found: {self.structure_file}") + self.assertTrue(self.dtm_file.exists(), f"dtm not found: {self.dtm_file}") + + def test_decimator_1_with_structure(self): + + geology_layer = QgsVectorLayer(str(self.geology_file), "geology", "ogr") + structure_layer = QgsVectorLayer(str(self.structure_file), "structure", "ogr") + dtm_layer = QgsRasterLayer(str(self.dtm_file), "dtm") + + self.assertTrue(geology_layer.isValid(), "geology layer should be valid") + self.assertTrue(structure_layer.isValid(), "structure layer should be valid") + self.assertTrue(dtm_layer.isValid(), "dtm layer should be valid") + self.assertGreater(geology_layer.featureCount(), 0, "geology layer should have features") + self.assertGreater(structure_layer.featureCount(), 0, "structure layer should have features") + + QgsMessageLog.logMessage(f"geology layer valid: {geology_layer.isValid()}", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"structure layer valid: {structure_layer.isValid()}", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"dtm layer valid: {dtm_layer.isValid()}", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"dtm source: {dtm_layer.source()}", "TestDecimator", Qgis.Critical) + + QgsMessageLog.logMessage(f"geology layer: {geology_layer.featureCount()} features", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"structure layer: {structure_layer.featureCount()} features", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"spatial data- structure layer", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"sampler type: Decimator", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"decimation: 1", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"dtm: {self.dtm_file.name}", "TestDecimator", Qgis.Critical) + + algorithm = SamplerAlgorithm() + algorithm.initAlgorithm() + + parameters = { + 'DTM': dtm_layer, + 'GEOLOGY': geology_layer, + 'SPATIAL_DATA': structure_layer, + 'SAMPLER_TYPE': 0, + 'DECIMATION': 1, + 'SPACING': 200.0, + 'SAMPLED_CONTACTS': 'memory:decimated_points' + } + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + + try: + QgsMessageLog.logMessage("Starting decimator sampler algorithm...", "TestDecimator", Qgis.Critical) + + result = algorithm.processAlgorithm(parameters, context, feedback) + + QgsMessageLog.logMessage(f"Result: {result}", "TestDecimator", Qgis.Critical) + + self.assertIsNotNone(result, "result should not be None") + self.assertIn('SAMPLED_CONTACTS', result, "Result should contain SAMPLED_CONTACTS key") + + QgsMessageLog.logMessage("Decimator sampler test completed successfully!", "TestDecimator", Qgis.Critical) + + except Exception as e: + QgsMessageLog.logMessage(f"Decimator sampler test error: {str(e)}", "TestDecimator", Qgis.Critical) + QgsMessageLog.logMessage(f"Error type: {type(e).__name__}", "TestDecimator", Qgis.Critical) + + import traceback + QgsMessageLog.logMessage(f"Full traceback:\n{traceback.format_exc()}", "TestDecimator", Qgis.Critical) + raise + + finally: + QgsMessageLog.logMessage("=" * 50, "TestDecimator", Qgis.Critical) + + @classmethod + def tearDownClass(cls): + QgsApplication.processingRegistry().removeProvider(cls.provider) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/qgis/test_sampler_spacing.py b/tests/qgis/test_sampler_spacing.py new file mode 100644 index 0000000..a542b85 --- /dev/null +++ b/tests/qgis/test_sampler_spacing.py @@ -0,0 +1,80 @@ +import unittest +from pathlib import Path +from qgis.core import QgsVectorLayer, QgsProcessingContext, QgsProcessingFeedback, QgsMessageLog, Qgis, QgsApplication +from qgis.testing import start_app +from m2l.processing.algorithms.sampler import SamplerAlgorithm +from m2l.processing.provider import Map2LoopProvider + +class TestSamplerSpacing(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.qgs = start_app() + + cls.provider = Map2LoopProvider() + QgsApplication.processingRegistry().addProvider(cls.provider) + + def setUp(self): + self.test_dir = Path(__file__).parent + self.input_dir = self.test_dir / "input" + + self.geology_file = self.input_dir / "geol_clip_no_gaps.shp" + + self.assertTrue(self.geology_file.exists(), f"geology not found: {self.geology_file}") + + def test_spacing_50_with_geology(self): + + geology_layer = QgsVectorLayer(str(self.geology_file), "geology", "ogr") + + self.assertTrue(geology_layer.isValid(), "geology layer should be valid") + self.assertGreater(geology_layer.featureCount(), 0, "geology layer should have features") + + QgsMessageLog.logMessage(f"geology layer: {geology_layer.featureCount()} features", "TestSampler", Qgis.Critical) + QgsMessageLog.logMessage(f"spatial data- geology layer", "TestSampler", Qgis.Critical) + QgsMessageLog.logMessage(f"sampler type: Spacing", "TestSampler", Qgis.Critical) + QgsMessageLog.logMessage(f"spacing: 50", "TestSampler", Qgis.Critical) + + algorithm = SamplerAlgorithm() + algorithm.initAlgorithm() + + parameters = { + 'DTM': None, + 'GEOLOGY': None, + 'SPATIAL_DATA': geology_layer, + 'SAMPLER_TYPE': 1, + 'DECIMATION': 1, + 'SPACING': 50.0, + 'SAMPLED_CONTACTS': 'memory:sampled_points' + } + + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + + try: + QgsMessageLog.logMessage("Starting spacing sampler algorithm...", "TestSampler", Qgis.Critical) + + result = algorithm.processAlgorithm(parameters, context, feedback) + + QgsMessageLog.logMessage(f"Result: {result}", "TestSampler", Qgis.Critical) + + self.assertIsNotNone(result, "result should not be None") + self.assertIn('SAMPLED_CONTACTS', result, "Result should contain SAMPLED_CONTACTS key") + + QgsMessageLog.logMessage("Spacing sampler test completed successfully!", "TestSampler", Qgis.Critical) + + except Exception as e: + QgsMessageLog.logMessage(f"Spacing sampler test error: {str(e)}", "TestSampler", Qgis.Critical) + QgsMessageLog.logMessage(f"Error type: {type(e).__name__}", "TestSampler", Qgis.Critical) + import traceback + QgsMessageLog.logMessage(f"Full traceback:\n{traceback.format_exc()}", "TestSampler", Qgis.Critical) + raise + + finally: + QgsMessageLog.logMessage("=" * 50, "TestSampler", Qgis.Critical) + + @classmethod + def tearDownClass(cls): + QgsApplication.processingRegistry().removeProvider(cls.provider) + +if __name__ == '__main__': + unittest.main()