From 47f529b2f4a1724e05ca9c8bda99ae6c94a1ffea Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 27 Aug 2024 14:47:22 -0700 Subject: [PATCH 01/33] First work on adding more extensive results testing for layers --- tests/test_layer_dimensions.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/test_layer_dimensions.py b/tests/test_layer_dimensions.py index 15768d6..7449668 100644 --- a/tests/test_layer_dimensions.py +++ b/tests/test_layer_dimensions.py @@ -1,3 +1,5 @@ +import numpy as np + from city_metrix.layers import NdviSentinel2 from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 from tests.tools import post_process_layer @@ -7,12 +9,32 @@ def test_ndvi_dimensions(): data = NdviSentinel2(year=2023).get_data(BBOX) - data_for_map = post_process_layer(data, value_threshold=0.4, convert_to_percentage=True) + thinned_data = post_process_layer(data, value_threshold=0.4, convert_to_percentage=True) expected_min = 0 - actual_min = data_for_map.values.min() + actual_min = thinned_data.values.min() expected_max = 85 - actual_max = data_for_map.values.max() + actual_max = thinned_data.values.max() + + # Bounding distribution of values + full_count = thinned_data.size + min_count = thinned_data.values[thinned_data.values == expected_min].size + min_count_pct = get_rounded_pct(full_count, min_count, 1) + max_count = thinned_data.values[thinned_data.values == expected_max].size + max_count_pct = get_rounded_pct(full_count, max_count, 1) + + mid_range_value = round((expected_min+expected_max)/2) + mid_count = thinned_data.values[thinned_data.values == mid_range_value].size + mid_count_pct = get_rounded_pct(full_count, mid_count, 1) + + # Value range + assert actual_min == 0 + assert actual_max == 85 + # Value distribution + assert min_count_pct == 6.7 + assert max_count_pct == 0 + assert mid_count_pct == 0.3 + - assert actual_min == expected_min - assert actual_max == expected_max +def get_rounded_pct(full_count, this_count, precision): + return round((this_count/full_count)*100, precision) From 6014a9cf42de5bdfbd882e6d691bbf03af82b71b Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 27 Aug 2024 17:25:48 -0700 Subject: [PATCH 02/33] Implemented additional dimensional tests on layers --- .../conftest.py | 15 +- tests/test_layer_dimensions.py | 128 +++++++++++++++--- tests/test_layers.py | 84 ++++-------- tests/tools.py | 2 +- 4 files changed, 145 insertions(+), 84 deletions(-) diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py index 5882053..8684c0e 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py @@ -22,15 +22,16 @@ def pytest_configure(config): qgis_project_file = 'layers_for_br_lauro_de_freitas.qgz' - source_folder = os.path.dirname(__file__) - target_folder = get_target_folder_path() - create_target_folder(target_folder, True) + if RUN_DUMPS is True: + source_folder = os.path.dirname(__file__) + target_folder = get_target_folder_path() + create_target_folder(target_folder, True) - source_qgis_file = os.path.join(source_folder, qgis_project_file) - target_qgis_file = os.path.join(target_folder, qgis_project_file) - shutil.copyfile(source_qgis_file, target_qgis_file) + source_qgis_file = os.path.join(source_folder, qgis_project_file) + target_qgis_file = os.path.join(target_folder, qgis_project_file) + shutil.copyfile(source_qgis_file, target_qgis_file) - print("\n\033[93m QGIS project file and layer files written to folder %s.\033[0m\n" % target_folder) + print("\n\033[93m QGIS project file and layer files written to folder %s.\033[0m\n" % target_folder) @pytest.fixture def target_folder(): diff --git a/tests/test_layer_dimensions.py b/tests/test_layer_dimensions.py index 7449668..ef2d5f3 100644 --- a/tests/test_layer_dimensions.py +++ b/tests/test_layer_dimensions.py @@ -1,40 +1,128 @@ -import numpy as np +import ee +import pytest -from city_metrix.layers import NdviSentinel2 +from city_metrix.layers import NdviSentinel2, TreeCover, Albedo, AlosDSM from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 from tests.tools import post_process_layer +from city_metrix.layers.layer import get_image_collection +EE_IMAGE_DIMENSION_TOLERANCE = 1 # Tolerance compensates for variable results from GEE service COUNTRY_CODE_FOR_BBOX = 'BRA' BBOX = BBOX_BRA_LAURO_DE_FREITAS_1 +def test_read_image_collection(): + ic = ee.ImageCollection("ESA/WorldCover/v100") + data = get_image_collection(ic, BBOX, 10, "test") + + expected_crs = 32724 + expected_x_dimension = 187 + expected_y_dimension = 199 + + assert data.rio.crs == expected_crs + assert ( + pytest.approx(expected_x_dimension, rel=EE_IMAGE_DIMENSION_TOLERANCE) == "x", + pytest.approx(expected_y_dimension, rel=EE_IMAGE_DIMENSION_TOLERANCE) == "y" + ) + +def test_read_image_collection_scale(): + ic = ee.ImageCollection("ESA/WorldCover/v100") + data = get_image_collection(ic, BBOX, 100, "test") + expected_x_dimension = 19 + expected_y_dimension = 20 + assert data.dims == {"x": expected_x_dimension, "y": expected_y_dimension} + +def test_albedo_dimensions(): + data = Albedo().get_data(BBOX) + analysis_data = post_process_layer(data, value_threshold=0.1, convert_to_percentage=True) + + expected_min = 0 + expected_max = 34 + expected_peak_value = 15 + # peak_value, peak_count = get_count_by_value(analysis_data, expected_min, expected_max) + + # Bounding values + actual_min = analysis_data.values.min() + actual_max = analysis_data.values.max() + + # Peak frequency + full_count = analysis_data.size + mid_count_pct = get_value_percent(analysis_data, expected_peak_value, full_count, 0) + + # Value range + assert actual_min == expected_min + assert actual_max == expected_max + # Peak frequency + assert mid_count_pct == 21 + +def test_alos_dsm_dimensions(): + analysis_data = AlosDSM().get_data(BBOX) + + expected_min = 16 + expected_max = 86 + expected_peak_value = 56 + peak_value, peak_count = get_count_by_value(analysis_data, expected_min, expected_max) + + # Bounding values + actual_min = analysis_data.values.min() + actual_max = analysis_data.values.max() + + # Peak frequency + full_count = analysis_data.size + mid_count_pct = get_value_percent(analysis_data, expected_peak_value, full_count, 0) + + # Value range + assert actual_min == expected_min + assert actual_max == expected_max + # Peak frequency + assert mid_count_pct == 3 + def test_ndvi_dimensions(): data = NdviSentinel2(year=2023).get_data(BBOX) - thinned_data = post_process_layer(data, value_threshold=0.4, convert_to_percentage=True) + analysis_data = post_process_layer(data, value_threshold=0.4, convert_to_percentage=True) expected_min = 0 - actual_min = thinned_data.values.min() expected_max = 85 - actual_max = thinned_data.values.max() + expected_peak_value = 78 + # peak_value, peak_count = get_count_by_value(analysis_data, expected_min, expected_max) - # Bounding distribution of values - full_count = thinned_data.size - min_count = thinned_data.values[thinned_data.values == expected_min].size - min_count_pct = get_rounded_pct(full_count, min_count, 1) - max_count = thinned_data.values[thinned_data.values == expected_max].size - max_count_pct = get_rounded_pct(full_count, max_count, 1) + # Bounding values + actual_min = analysis_data.values.min() + actual_max = analysis_data.values.max() - mid_range_value = round((expected_min+expected_max)/2) - mid_count = thinned_data.values[thinned_data.values == mid_range_value].size - mid_count_pct = get_rounded_pct(full_count, mid_count, 1) + # Peak frequency + full_count = analysis_data.size + mid_count_pct = get_value_percent(analysis_data, expected_peak_value, full_count, 0) # Value range - assert actual_min == 0 - assert actual_max == 85 - # Value distribution - assert min_count_pct == 6.7 - assert max_count_pct == 0 - assert mid_count_pct == 0.3 + assert actual_min == expected_min + assert actual_max == expected_max + # Peak frequency + assert mid_count_pct == 11 + + +def test_tree_cover(): + actual = TreeCover().get_data(BBOX).mean() + expected = 54.0 + tolerance = 0.1 + assert ( + pytest.approx(expected, rel=tolerance) == actual + ) +def get_value_percent(data, value, full_count, precision): + count_for_value = data.values[data.values == value].size + percent_of_cells_with_value = get_rounded_pct(full_count, count_for_value, precision) + return percent_of_cells_with_value def get_rounded_pct(full_count, this_count, precision): return round((this_count/full_count)*100, precision) + +def get_count_by_value(data, min_value, max_value): + peak_value = None + peak_count = 0 + for x in range(min_value, max_value): + count = data.values[data.values == x].size + if count > peak_count: + peak_count = count + peak_value = x + + return peak_value, peak_count \ No newline at end of file diff --git a/tests/test_layers.py b/tests/test_layers.py index bfcf4a0..2ac6280 100644 --- a/tests/test_layers.py +++ b/tests/test_layers.py @@ -1,4 +1,3 @@ -import ee import pytest from city_metrix.layers import ( @@ -24,92 +23,70 @@ UrbanLandUse, WorldPop ) -from city_metrix.layers.layer import get_image_collection from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 -EE_IMAGE_DIMENSION_TOLERANCE = 1 # Tolerance compensates for variable results from GEE service # Tests are implemented for the same bounding box in Brazil. COUNTRY_CODE_FOR_BBOX = 'BRA' BBOX = BBOX_BRA_LAURO_DE_FREITAS_1 def test_albedo(): - assert Albedo().get_data(BBOX).mean() + count = Albedo().get_data(BBOX).count() + assert count def test_alos_dsm(): - mean = AlosDSM().get_data(BBOX).mean() - assert mean + count = AlosDSM().get_data(BBOX).count() + assert count def test_average_net_building_height(): - assert AverageNetBuildingHeight().get_data(BBOX).mean() + count = AverageNetBuildingHeight().get_data(BBOX).count() + assert count def test_esa_world_cover(): + land_cover_class = EsaWorldCoverClass.BUILT_UP count = ( - EsaWorldCover(land_cover_class=EsaWorldCoverClass.BUILT_UP) + EsaWorldCover(land_cover_class=land_cover_class) .get_data(BBOX) .count() ) assert count -def test_read_image_collection(): - ic = ee.ImageCollection("ESA/WorldCover/v100") - data = get_image_collection(ic, BBOX, 10, "test") - - expected_crs = 32724 - expected_x_dimension = 187 - expected_y_dimension = 199 - - assert data.rio.crs == expected_crs - assert ( - pytest.approx(expected_x_dimension, rel=EE_IMAGE_DIMENSION_TOLERANCE) == "x", - pytest.approx(expected_y_dimension, rel=EE_IMAGE_DIMENSION_TOLERANCE) == "y" - ) - - -def test_read_image_collection_scale(): - ic = ee.ImageCollection("ESA/WorldCover/v100") - data = get_image_collection(ic, BBOX, 100, "test") - expected_x_dimension = 19 - expected_y_dimension = 20 - assert data.dims == {"x": expected_x_dimension, "y": expected_y_dimension} - - def test_high_land_surface_temperature(): - data = HighLandSurfaceTemperature().get_data(BBOX) - assert data.any() + count = HighLandSurfaceTemperature().get_data(BBOX).count() + assert count def test_land_surface_temperature(): - mean_lst = LandSurfaceTemperature().get_data(BBOX).mean() - assert mean_lst + count = LandSurfaceTemperature().get_data(BBOX).count() + assert count @pytest.mark.skip(reason="layer is deprecated") def test_landsat_collection_2(): bands = ["blue"] - data = LandsatCollection2(bands).get_data(BBOX) - assert data.any() + count = LandsatCollection2(bands).get_data(BBOX).count() + assert count def test_nasa_dem(): - mean = NasaDEM().get_data(BBOX).mean() - assert mean + count = NasaDEM().get_data(BBOX).count() + assert count def test_natural_areas(): - data = NaturalAreas().get_data(BBOX) - assert data.any() + count = NaturalAreas().get_data(BBOX).count() + assert count def test_ndvi_sentinel2(): - data = NdviSentinel2(year=2023).get_data(BBOX) - assert data is not None + count = NdviSentinel2(year=2023).get_data(BBOX).count() + assert count def test_openbuildings(): - count = OpenBuildings(COUNTRY_CODE_FOR_BBOX).get_data(BBOX).count().sum() + count = OpenBuildings(COUNTRY_CODE_FOR_BBOX).get_data(BBOX).count() assert count @@ -118,21 +95,20 @@ def test_open_street_map(): OpenStreetMap(osm_class=OpenStreetMapClass.ROAD) .get_data(BBOX) .count() - .sum() ) assert count def test_overture_buildings(): - count = OvertureBuildings().get_data(BBOX).count().sum() + count = OvertureBuildings().get_data(BBOX).count() assert count @pytest.mark.skip(reason="layer is deprecated") def test_sentinel_2_level2(): sentinel_2_bands = ["green"] - data = Sentinel2Level2(sentinel_2_bands).get_data(BBOX) - assert data.any() + count = Sentinel2Level2(sentinel_2_bands).get_data(BBOX).count() + assert count def test_smart_surface_lulc(): @@ -144,12 +120,8 @@ def test_tree_canopy_height(): assert count def test_tree_cover(): - actual = TreeCover().get_data(BBOX).mean() - expected = 54.0 - tolerance = 0.1 - assert ( - pytest.approx(expected, rel=tolerance) == actual - ) + count = TreeCover().get_data(BBOX).count() + assert count def test_urban_land_use(): @@ -157,5 +129,5 @@ def test_urban_land_use(): def test_world_pop(): - data = WorldPop().get_data(BBOX) - assert data.any() + count = WorldPop().get_data(BBOX).count() + assert count diff --git a/tests/tools.py b/tests/tools.py index 99425df..4ca5939 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -10,7 +10,7 @@ def post_process_layer(data, value_threshold=0.4, convert_to_percentage=True): """ # Remove values less than the specified threshold if value_threshold is not None: - data = data.where(data >= value_threshold) + data = data.where(data >= value_threshold, drop=True) # Convert to percentage in byte data_type if convert_to_percentage is True: From 10b22056a1405b462b302786f9d4cb4225746a01 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Mon, 9 Sep 2024 19:57:37 -0700 Subject: [PATCH 03/33] Extending testing --- city_metrix/layers/impervious_surface.py | 10 ++++++++-- .../conftest.py | 2 +- .../test_write_layers_to_qgis_files.py | 9 ++++++++- tests/test_layer_parameters.py | 14 +++++++++++++- tests/test_layers.py | 9 +++++++-- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/city_metrix/layers/impervious_surface.py b/city_metrix/layers/impervious_surface.py index 74e624d..ea6772e 100644 --- a/city_metrix/layers/impervious_surface.py +++ b/city_metrix/layers/impervious_surface.py @@ -7,8 +7,14 @@ class ImperviousSurface(Layer): - def __init__(self, **kwargs): + """ + Attributes: + spatial_resolution: raster resolution in meters (see https://github.com/stac-extensions/raster) + """ + + def __init__(self, spatial_resolution=100, **kwargs): super().__init__(**kwargs) + self.spatial_resolution = spatial_resolution def get_data(self, bbox): # load impervious_surface @@ -19,5 +25,5 @@ def get_data(self, bbox): .sum() ) - data = get_image_collection(imperv_surf, bbox, 100, "imperv surf") + data = get_image_collection(imperv_surf, bbox, self.spatial_resolution, "imperv surf") return data.change_year_index diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py index 13c6e48..d933c20 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py @@ -10,7 +10,7 @@ # RUN_DUMPS is the master control for whether the writes and tests are executed # Setting RUN_DUMPS to True turns on code execution. # Values should normally be set to False in order to avoid unnecessary execution. -RUN_DUMPS = False +RUN_DUMPS = True # Multiplier applied to the default spatial_resolution of the layer # Use value of 1 for default resolution. diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py index 5e89328..a4dd2de 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py @@ -21,7 +21,7 @@ TreeCanopyHeight, TreeCover, UrbanLandUse, - WorldPop, Layer + WorldPop, Layer, ImperviousSurface ) from .conftest import RUN_DUMPS, prep_output_path, verify_file_is_populated from ...tools.general_tools import get_class_default_spatial_resolution @@ -62,6 +62,13 @@ def test_write_high_land_surface_temperature(target_folder, bbox_info, target_sp HighLandSurfaceTemperature(spatial_resolution=target_resolution).write(bbox_info.bounds, file_path, tile_degrees=None) assert verify_file_is_populated(file_path) +@pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') +def test_write_impervious_surface(target_folder, bbox_info, target_spatial_resolution_multiplier): + file_path = prep_output_path(target_folder, 'impervious_surface.tif') + target_resolution = target_spatial_resolution_multiplier * get_class_default_spatial_resolution(ImperviousSurface()) + LandSurfaceTemperature(spatial_resolution=target_resolution).write(bbox_info.bounds, file_path, tile_degrees=None) + assert verify_file_is_populated(file_path) + @pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') def test_write_land_surface_temperature(target_folder, bbox_info, target_spatial_resolution_multiplier): file_path = prep_output_path(target_folder, 'land_surface_temperature.tif') diff --git a/tests/test_layer_parameters.py b/tests/test_layer_parameters.py index ec2127b..68f97a9 100644 --- a/tests/test_layer_parameters.py +++ b/tests/test_layer_parameters.py @@ -9,14 +9,17 @@ AverageNetBuildingHeight, BuiltUpHeight, EsaWorldCover, EsaWorldCoverClass, + HighLandSurfaceTemperature, + ImperviousSurface, LandSurfaceTemperature, NasaDEM, NaturalAreas, NdviSentinel2, + OpenStreetMap, TreeCanopyHeight, TreeCover, UrbanLandUse, - WorldPop, OpenStreetMap, HighLandSurfaceTemperature + WorldPop ) from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 from tests.tools.general_tools import get_class_from_instance, get_class_default_spatial_resolution @@ -90,6 +93,15 @@ def test_high_land_surface_temperature_downsampled_spatial_resolution(self): assert pytest.approx(target_downsized_res, rel=RESOLUTION_TOLERANCE) == estimated_actual_res assert downsizing_is_within_tolerances + def test_impervious_surface_downsampled_spatial_resolution(self): + class_instance = ImperviousSurface() + default_res_data, downsized_res_data, target_downsized_res, estimated_actual_res = ( + _get_sample_data(class_instance, BBOX, DOWNSIZE_FACTOR)) + downsizing_is_within_tolerances = _evaluate_raster_value(default_res_data, downsized_res_data) + + assert pytest.approx(target_downsized_res, rel=RESOLUTION_TOLERANCE) == estimated_actual_res + assert downsizing_is_within_tolerances + def test_land_surface_temperature_downsampled_spatial_resolution(self): class_instance = LandSurfaceTemperature() default_res_data, downsized_res_data, target_downsized_res, estimated_actual_res = ( diff --git a/tests/test_layers.py b/tests/test_layers.py index b48624a..d26a917 100644 --- a/tests/test_layers.py +++ b/tests/test_layers.py @@ -4,7 +4,7 @@ Albedo, AlosDSM, AverageNetBuildingHeight, - NdviSentinel2, + BuiltUpHeight, EsaWorldCover, EsaWorldCoverClass, HighLandSurfaceTemperature, @@ -13,6 +13,7 @@ LandSurfaceTemperature, NasaDEM, NaturalAreas, + NdviSentinel2, OpenBuildings, OpenStreetMap, OpenStreetMapClass, @@ -42,6 +43,10 @@ def test_average_net_building_height(): data = AverageNetBuildingHeight().get_data(BBOX) assert np.size(data) > 0 +def test_built_up_height(): + data = BuiltUpHeight().get_data(BBOX) + assert np.size(data) > 0 + def test_esa_world_cover(): land_cover_class = EsaWorldCoverClass.BUILT_UP data = EsaWorldCover(land_cover_class=land_cover_class).get_data(BBOX) @@ -53,7 +58,7 @@ def test_high_land_surface_temperature(): def test_impervious_surface(): data = ImperviousSurface().get_data(BBOX) - assert data.any() + assert np.size(data) > 0 def test_land_surface_temperature(): data = LandSurfaceTemperature().get_data(BBOX) From 7977d11ec76d35c4476e364b2d9badd9f90bec3c Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Mon, 9 Sep 2024 22:05:09 -0700 Subject: [PATCH 04/33] Additional tests --- city_metrix/layers/ndvi_sentinel2_gee.py | 2 +- .../conftest.py | 2 +- .../layers_for_br_lauro_de_freitas.qgz | Bin 24158 -> 25861 bytes tests/test_layer_parameters.py | 51 +++++++++++++++--- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/city_metrix/layers/ndvi_sentinel2_gee.py b/city_metrix/layers/ndvi_sentinel2_gee.py index e49b3d3..fce3021 100644 --- a/city_metrix/layers/ndvi_sentinel2_gee.py +++ b/city_metrix/layers/ndvi_sentinel2_gee.py @@ -12,7 +12,7 @@ class NdviSentinel2(Layer): Notebook: https://github.com/wri/cities-cities4forests-indicators/blob/dev-eric/scripts/extract-VegetationCover.ipynb Reference: https://en.wikipedia.org/wiki/Normalized_difference_vegetation_index """ - def __init__(self, year=None, spatial_resolution=10, **kwargs): + def __init__(self, year=2021, spatial_resolution=10, **kwargs): super().__init__(**kwargs) self.year = year self.spatial_resolution = spatial_resolution diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py index d933c20..13c6e48 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/conftest.py @@ -10,7 +10,7 @@ # RUN_DUMPS is the master control for whether the writes and tests are executed # Setting RUN_DUMPS to True turns on code execution. # Values should normally be set to False in order to avoid unnecessary execution. -RUN_DUMPS = True +RUN_DUMPS = False # Multiplier applied to the default spatial_resolution of the layer # Use value of 1 for default resolution. diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz b/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz index 759515e6e49022f8cbd0bbc699ed843efc6440b7..eff75bb16ff05d4b7e2c4b6471720fbc8a5cb1c9 100644 GIT binary patch delta 23912 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%0A;{z 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 ZZbJXZr8cEOBQZ~UY|4zuVtoho`5%i&kjMZ4 delta 22219 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

*cQEYVzLS-W%>dY`NkLanLI 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

8TupP=@`vyHp7+`&LsIli*O8>eYmY|VoXkP=$9 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 B!%+YL diff --git a/tests/test_layer_parameters.py b/tests/test_layer_parameters.py index 68f97a9..a1eb5e8 100644 --- a/tests/test_layer_parameters.py +++ b/tests/test_layer_parameters.py @@ -19,7 +19,7 @@ TreeCanopyHeight, TreeCover, UrbanLandUse, - WorldPop + WorldPop, OpenBuildings ) from tests.resources.bbox_constants import BBOX_BRA_LAURO_DE_FREITAS_1 from tests.tools.general_tools import get_class_from_instance, get_class_default_spatial_resolution @@ -191,8 +191,7 @@ def test_null_spatial_resolution(self): with pytest.raises(Exception) as e_info: _get_modified_resolution_data(class_instance, spatial_resolution, BBOX) -class TestThreshold: - +class TestOtherParameters: def test_albedo_threshold(self): threshold=0.1 data = Albedo(threshold=threshold).get_data(BBOX) @@ -201,6 +200,44 @@ def test_albedo_threshold(self): assert threshold > max_albedo,\ f"Maximum value ({max_albedo}) in Albedo dataset is not < threshold of {threshold}." + def test_albedo_dates(self): + with pytest.raises(Exception) as e_info: + Albedo(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + + with pytest.raises(Exception) as e_info: + Albedo(start_date="2021-01-01", end_date=None).get_data(BBOX) + + def test_high_land_surface_temperature_dates(self): + with pytest.raises(Exception) as e_info: + HighLandSurfaceTemperature(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + + def test_land_surface_temperature_dates(self): + with pytest.raises(Exception) as e_info: + LandSurfaceTemperature(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + + with pytest.raises(Exception) as e_info: + LandSurfaceTemperature(start_date="2021-01-01", end_date=None).get_data(BBOX) + + def test_ndvi_sentinel2_dates(self): + with pytest.raises(Exception) as e_info: + data = NdviSentinel2(Year=None).get_data(BBOX) + + with pytest.raises(Exception) as e_info: + NdviSentinel2(Year="1970").get_data(BBOX) + + def test_open_buildings_country(self): + with pytest.raises(Exception) as e_info: + OpenBuildings(country="ZZZ").get_data(BBOX) + + def test_tree_cover_min_max_cover(self): + data = TreeCover(min_tree_cover = 150).get_data(BBOX) + non_null_cells = data.values[~np.isnan(data)].size + assert non_null_cells == 0 + + data = TreeCover(max_tree_cover = -1).get_data(BBOX) + non_null_cells = data.values[~np.isnan(data)].size + assert non_null_cells == 0 + def test_function_validate_layer_instance(): is_valid, except_str = _validate_layer_instance('t') @@ -288,12 +325,12 @@ def _evaluate_raster_value(raw_data, downsized_data): # Below values where determined through trial and error evaluation of results in QGIS ratio_tolerance = 0.2 normalized_rmse_tolerance = 0.3 - ssim_index_tolerance = 0.6 + populated_raw_data_ratio = _get_populate_ratio(raw_data) populated_downsized_data_ratio = _get_populate_ratio(raw_data) - diff = abs(populated_raw_data_ratio - populated_downsized_data_ratio) - ratio_eval = True if diff <= ratio_tolerance else False + ratio_diff = abs(populated_raw_data_ratio - populated_downsized_data_ratio) + ratio_eval = True if ratio_diff <= ratio_tolerance else False filled_raw_data = raw_data.fillna(0) filled_downsized_data = downsized_data.fillna(0) @@ -320,9 +357,9 @@ def _evaluate_raster_value(raw_data, downsized_data): matching_rmse = True if normalized_rmse < normalized_rmse_tolerance else False # Calculate and evaluate Structural Similarity Index (SSIM) + ssim_index_tolerance = 0.6 if (processed_downsized_data_np.size > 100 and ratio_diff <= 0.1) else 0.4 ssim_index, _ = ssim(processed_downsized_data_np, processed_raw_data_np, full=True, data_range=max_val) matching_ssim = True if ssim_index > ssim_index_tolerance else False - results_match = True if (ratio_eval & matching_rmse & matching_ssim) else False return results_match From 0271e197ca22ded8015a74cabbfb755f4d5dd660 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 10 Sep 2024 10:39:10 -0700 Subject: [PATCH 05/33] Changed output file type for vector layers --- .../layers_for_br_lauro_de_freitas.qgz | Bin 25861 -> 25729 bytes .../test_write_layers_to_qgis_files.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz b/tests/resources/layer_dumps_for_br_lauro_de_freitas/layers_for_br_lauro_de_freitas.qgz index eff75bb16ff05d4b7e2c4b6471720fbc8a5cb1c9..1cc53ce2df38d8b5e7bd55634a44fd0b186de69d 100644 GIT binary patch delta 23786 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 z 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 u#Q}nnsf)IZm<2Y!BmM_5TXg9F delta 23860 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 arnhDK6GCk1vB)ixO>Eh4*==uNUjG3!vv%D8 diff --git a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py index a4dd2de..160d7bc 100644 --- a/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py +++ b/tests/resources/layer_dumps_for_br_lauro_de_freitas/test_write_layers_to_qgis_files.py @@ -107,7 +107,7 @@ def test_write_ndvi_sentinel2_gee(target_folder, bbox_info, target_spatial_resol @pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') def test_write_openbuildings(target_folder, bbox_info, target_spatial_resolution_multiplier): - file_path = prep_output_path(target_folder, 'open_buildings.tif') + file_path = prep_output_path(target_folder, 'open_buildings.geojson') OpenBuildings(bbox_info.country).write(bbox_info.bounds, file_path, tile_degrees=None) assert verify_file_is_populated(file_path) @@ -120,7 +120,7 @@ def test_write_openbuildings(target_folder, bbox_info, target_spatial_resolution @pytest.mark.skipif(RUN_DUMPS == False, reason='Skipping since RUN_DUMPS set to False') def test_write_overture_buildings(target_folder, bbox_info, target_spatial_resolution_multiplier): - file_path = prep_output_path(target_folder, 'overture_buildings.tif') + file_path = prep_output_path(target_folder, 'overture_buildings.geojson') OvertureBuildings().write(bbox_info.bounds, file_path, tile_degrees=None) assert verify_file_is_populated(file_path) From 949e097165b50c223dcfec23b6e9c1825b7b7d39 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 10 Sep 2024 10:55:54 -0700 Subject: [PATCH 06/33] Delete populated directories during diretory cleanup --- tests/tools/general_tools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tools/general_tools.py b/tests/tools/general_tools.py index 2e29159..9e310b6 100644 --- a/tests/tools/general_tools.py +++ b/tests/tools/general_tools.py @@ -1,4 +1,5 @@ import os +import shutil import tempfile import numpy as np @@ -19,6 +20,8 @@ def remove_all_files_in_directory(directory): # Check if it is a file and remove it if os.path.isfile(file_path): os.remove(file_path) + else: + shutil.rmtree(file_path) except Exception as e: print(f"Error: {e}") From a3936fecd317e8de35975cc9148fc852cf031c14 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Tue, 10 Sep 2024 14:18:06 -0700 Subject: [PATCH 07/33] Added metrics tests --- tests/test_metrics.py | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/tests/test_metrics.py b/tests/test_metrics.py index eb2c440..dcd731b 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -2,17 +2,43 @@ from .conftest import ZONES -def test_urban_open_space(): - indicator = urban_open_space(ZONES) - assert indicator.size == 100 +def test_built_land_with_high_lst(): + indicator = built_land_with_high_land_surface_temperature(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size def test_built_land_with_low_surface_reflectivity(): indicator = built_land_with_low_surface_reflectivity(ZONES) - assert indicator.size == 100 + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size -def test_high_lst(): - indicator = built_land_with_high_land_surface_temperature(ZONES) - assert indicator.size == 100 +def test_built_land_without_tree_cover(): + indicator = built_land_without_tree_cover(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size + + +def test_mean_tree_cover(): + indicator = mean_tree_cover(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size + +def test_natural_areas(): + indicator = natural_areas(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size + + +def test_urban_open_space(): + indicator = urban_open_space(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size From f2c09f874694668c13fcebbfbcdd3de33375c98b Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 07:41:10 -0700 Subject: [PATCH 08/33] Added exception evaluation --- tests/test_layer_parameters.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/test_layer_parameters.py b/tests/test_layer_parameters.py index a1eb5e8..703752c 100644 --- a/tests/test_layer_parameters.py +++ b/tests/test_layer_parameters.py @@ -190,6 +190,8 @@ def test_null_spatial_resolution(self): with pytest.raises(Exception) as e_info: _get_modified_resolution_data(class_instance, spatial_resolution, BBOX) + msg_correct = False if str(e_info.value).find("Spatial_resolution cannot be None.") == -1 else True + assert msg_correct class TestOtherParameters: def test_albedo_threshold(self): @@ -203,32 +205,49 @@ def test_albedo_threshold(self): def test_albedo_dates(self): with pytest.raises(Exception) as e_info: Albedo(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + msg_correct = False if str(e_info.value).find("max() arg is an empty sequence") == -1 else True + assert msg_correct with pytest.raises(Exception) as e_info: Albedo(start_date="2021-01-01", end_date=None).get_data(BBOX) + msg_correct = False if str(e_info.value).find("max() arg is an empty sequence") == -1 else True + assert msg_correct def test_high_land_surface_temperature_dates(self): with pytest.raises(Exception) as e_info: HighLandSurfaceTemperature(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + msg_correct = False if str(e_info.value).find("Dictionary does not contain key: 'date'") == -1 else True + assert msg_correct def test_land_surface_temperature_dates(self): with pytest.raises(Exception) as e_info: LandSurfaceTemperature(start_date="2021-01-01", end_date="2021-01-02").get_data(BBOX) + msg_correct = False if str(e_info.value).find("max() arg is an empty sequence") == -1 else True + assert msg_correct with pytest.raises(Exception) as e_info: LandSurfaceTemperature(start_date="2021-01-01", end_date=None).get_data(BBOX) + msg_correct = False if str(e_info.value).find("max() arg is an empty sequence") == -1 else True + assert msg_correct def test_ndvi_sentinel2_dates(self): with pytest.raises(Exception) as e_info: - data = NdviSentinel2(Year=None).get_data(BBOX) + data = NdviSentinel2(year=None).get_data(BBOX) + msg_correct = False if str(e_info.value).find("get_data() requires a year value") == -1 else True + assert msg_correct with pytest.raises(Exception) as e_info: - NdviSentinel2(Year="1970").get_data(BBOX) + NdviSentinel2(year="1970").get_data(BBOX) + msg_correct = False if str(e_info.value).find("max() arg is an empty sequence") == -1 else True + assert msg_correct def test_open_buildings_country(self): with pytest.raises(Exception) as e_info: OpenBuildings(country="ZZZ").get_data(BBOX) + msg_correct = False if str(e_info.value).find("projects/sat-io/open-datasets/VIDA_COMBINED/ZZZ' not found.") == -1 else True + assert msg_correct + def test_tree_cover_min_max_cover(self): data = TreeCover(min_tree_cover = 150).get_data(BBOX) non_null_cells = data.values[~np.isnan(data)].size From c700ca18dab290115854b49373461210c37bcf7e Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:02:25 -0700 Subject: [PATCH 09/33] First attempt for Conda Actions workflow --- .github/workflows/dev_ci_cd_conda.yml | 38 +++++++++++++++++++ .../{dev_ci_cd.yml => dev_ci_cd_pip.yml} | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/dev_ci_cd_conda.yml rename .github/workflows/{dev_ci_cd.yml => dev_ci_cd_pip.yml} (97%) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml new file mode 100644 index 0000000..72351b6 --- /dev/null +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -0,0 +1,38 @@ +name: Dev CIF API CI/CD PIP + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install Linux dependencies + run: | + sudo apt update + sudo apt install -y gdal-bin libgdal-dev + - name: Install Packages + run: | + conda update -n base conda + conda install --yes --file requirements.txt + conda install gdal + conda upgrade numpy + - name: Run Tests + env: + GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + run: | + pytest tests diff --git a/.github/workflows/dev_ci_cd.yml b/.github/workflows/dev_ci_cd_pip.yml similarity index 97% rename from .github/workflows/dev_ci_cd.yml rename to .github/workflows/dev_ci_cd_pip.yml index 6701372..9ff3fef 100644 --- a/.github/workflows/dev_ci_cd.yml +++ b/.github/workflows/dev_ci_cd_pip.yml @@ -1,4 +1,4 @@ -name: Dev CIF API CI/CD +name: Dev CIF API CI/CD PIP on: pull_request: From 1ba60ba004b850fbf0720b70cb8dadb3dd16bb3c Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:20:06 -0700 Subject: [PATCH 10/33] changed name of conda workflow --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 72351b6..cf019d4 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -1,4 +1,4 @@ -name: Dev CIF API CI/CD PIP +name: Dev CIF API CI/CD Conda on: pull_request: From 39066f0f9c56ab68a7db4d1afdb7816f291e0526 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:35:02 -0700 Subject: [PATCH 11/33] adding path --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index cf019d4..b417bc7 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages run: | conda update -n base conda - conda install --yes --file requirements.txt + conda install --yes --file .github/requirements.txt conda install gdal conda upgrade numpy - name: Run Tests From 2f3017ec144220d245c8c631e98ebacca27bffb5 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:45:51 -0700 Subject: [PATCH 12/33] tweaking workflows --- .github/environment.yml | 28 ++++++++++ .github/workflows/dev_ci_cd_conda.yml | 2 +- .github/workflows/dev_ci_cd_pip.yml | 74 +++++++++++++-------------- 3 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 .github/environment.yml diff --git a/.github/environment.yml b/.github/environment.yml new file mode 100644 index 0000000..a064834 --- /dev/null +++ b/.github/environment.yml @@ -0,0 +1,28 @@ +name: cities-cif +channels: + - conda-forge +dependencies: + - python=3.10 + - earthengine-api=0.1.379 + - geocube=0.4.2 + - geopandas=0.14.4 + - rioxarray=0.15.0 + - odc-stac=0.3.8 + - pystac-client=0.7.5 + - pytest=7.4.3 + - xarray-spatial=0.3.7 + - xee=0.0.15 + - utm=0.7.0 + - osmnx=1.9.0 + - dask[complete]=2023.11.0 + - matplotlib=3.8.2 + - jupyterlab=4.0.10 + - s3fs=2024.5.0 + - geemap=0.32.0 + - pip=23.3.1 + - boto3=1.34.124 + - scikit-learn=1.5.1 + - scikit-image==0.24.0 + - exactextract=0.2.0.dev252 + - pip: + - overturemaps==0.6.0 diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index b417bc7..e3ed63b 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages run: | conda update -n base conda - conda install --yes --file .github/requirements.txt + conda install --yes --file .github/environment.yml conda install gdal conda upgrade numpy - name: Run Tests diff --git a/.github/workflows/dev_ci_cd_pip.yml b/.github/workflows/dev_ci_cd_pip.yml index 9ff3fef..add6674 100644 --- a/.github/workflows/dev_ci_cd_pip.yml +++ b/.github/workflows/dev_ci_cd_pip.yml @@ -1,37 +1,37 @@ -name: Dev CIF API CI/CD PIP - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read -jobs: - build: - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: ["3.10"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install Linux dependencies - run: | - sudo apt update - sudo apt install -y gdal-bin libgdal-dev - - name: Install Packages - run: | - python -m pip install --upgrade pip - pip install -r .github/requirements.txt - pip install GDAL==`gdal-config --version` - - name: Run Tests - env: - GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} - GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - run: | - pytest tests +#name: Dev CIF API CI/CD PIP +# +#on: +# pull_request: +# workflow_dispatch: +# +#permissions: +# contents: read +#jobs: +# build: +# runs-on: ubuntu-latest +# strategy: +# max-parallel: 4 +# matrix: +# python-version: ["3.10"] +# +# steps: +# - uses: actions/checkout@v4 +# - name: Set up Python ${{ matrix.python-version }} +# uses: actions/setup-python@v5 +# with: +# python-version: ${{ matrix.python-version }} +# - name: Install Linux dependencies +# run: | +# sudo apt update +# sudo apt install -y gdal-bin libgdal-dev +# - name: Install Packages +# run: | +# python -m pip install --upgrade pip +# pip install -r .github/requirements.txt +# pip install GDAL==`gdal-config --version` +# - name: Run Tests +# env: +# GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} +# GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} +# run: | +# pytest tests From ec76dc0c5009b3d8555666b2d2ca30dfe50d3d21 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:57:02 -0700 Subject: [PATCH 13/33] switching to setup file --- .github/environment.yml | 28 --------------------- .github/setup.py | 36 +++++++++++++++++++++++++++ .github/workflows/dev_ci_cd_conda.yml | 2 +- 3 files changed, 37 insertions(+), 29 deletions(-) delete mode 100644 .github/environment.yml create mode 100644 .github/setup.py diff --git a/.github/environment.yml b/.github/environment.yml deleted file mode 100644 index a064834..0000000 --- a/.github/environment.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: cities-cif -channels: - - conda-forge -dependencies: - - python=3.10 - - earthengine-api=0.1.379 - - geocube=0.4.2 - - geopandas=0.14.4 - - rioxarray=0.15.0 - - odc-stac=0.3.8 - - pystac-client=0.7.5 - - pytest=7.4.3 - - xarray-spatial=0.3.7 - - xee=0.0.15 - - utm=0.7.0 - - osmnx=1.9.0 - - dask[complete]=2023.11.0 - - matplotlib=3.8.2 - - jupyterlab=4.0.10 - - s3fs=2024.5.0 - - geemap=0.32.0 - - pip=23.3.1 - - boto3=1.34.124 - - scikit-learn=1.5.1 - - scikit-image==0.24.0 - - exactextract=0.2.0.dev252 - - pip: - - overturemaps==0.6.0 diff --git a/.github/setup.py b/.github/setup.py new file mode 100644 index 0000000..7d9242c --- /dev/null +++ b/.github/setup.py @@ -0,0 +1,36 @@ +from setuptools import setup, find_packages + +setup( + name="city_metrix", + version="0.1.2", + description="Module to calculate various metrics on cities.", + packages=find_packages(), + include_package_data=True, + package_data={ + 'city_metrix.models.building_classifier': [ + 'building_classifier.pkl', + ], + }, + author="Justin Terry", + license="MIT", + install_requires=[ + "earthengine-api", + "geocube", + "odc-stac", + "geemap", + "pystac-client", + "xarray-spatial", + "xee", + "rioxarray", + "utm", + "osmnx", + "geopandas", + "s3fs", + "dask>=2023.11.0", + "boto3", + "exactextract<=0.2.0.dev252", + "overturemaps", + "scikit-learn>=1.5.1", + "scikit-image>=0.24.0" + ], +) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index e3ed63b..e0a012e 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages run: | conda update -n base conda - conda install --yes --file .github/environment.yml + conda install --yes --file .github/setup.yml conda install gdal conda upgrade numpy - name: Run Tests From 73ce0dd93f8fd33c58c619dfed18b9be7f517987 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 10:58:33 -0700 Subject: [PATCH 14/33] corrected file name --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index e0a012e..8a3b851 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages run: | conda update -n base conda - conda install --yes --file .github/setup.yml + conda install --yes --file .github/setup.py conda install gdal conda upgrade numpy - name: Run Tests From 6e8d565a323851c758bd12071825d36b4db6af1e Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 11:30:59 -0700 Subject: [PATCH 15/33] Changed version of exaxtextractr --- .github/environment.yml | 28 +++++++++++++++++++++ .github/setup.py | 36 --------------------------- .github/workflows/dev_ci_cd_conda.yml | 2 +- environment.yml | 4 +-- 4 files changed, 31 insertions(+), 39 deletions(-) create mode 100644 .github/environment.yml delete mode 100644 .github/setup.py diff --git a/.github/environment.yml b/.github/environment.yml new file mode 100644 index 0000000..addf333 --- /dev/null +++ b/.github/environment.yml @@ -0,0 +1,28 @@ +name: cities-cif +channels: + - conda-forge +dependencies: + - python=3.10 + - earthengine-api=0.1.379 + - geocube=0.4.2 + - geopandas=0.14.4 + - rioxarray=0.15.0 + - odc-stac=0.3.8 + - pystac-client=0.7.5 + - pytest=7.4.3 + - xarray-spatial=0.3.7 + - xee=0.0.15 + - utm=0.7.0 + - osmnx=1.9.0 + - dask[complete]=2023.11.0 + - matplotlib=3.8.2 + - jupyterlab=4.0.10 + - s3fs=2024.5.0 + - geemap=0.32.0 + - pip=23.3.1 + - boto3=1.34.124 + - scikit-learn=1.5.1 + - scikit-image=0.24.0 + - r-exactextractr=0.10.0 + - pip: + - overturemaps==0.6.0 diff --git a/.github/setup.py b/.github/setup.py deleted file mode 100644 index 7d9242c..0000000 --- a/.github/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="city_metrix", - version="0.1.2", - description="Module to calculate various metrics on cities.", - packages=find_packages(), - include_package_data=True, - package_data={ - 'city_metrix.models.building_classifier': [ - 'building_classifier.pkl', - ], - }, - author="Justin Terry", - license="MIT", - install_requires=[ - "earthengine-api", - "geocube", - "odc-stac", - "geemap", - "pystac-client", - "xarray-spatial", - "xee", - "rioxarray", - "utm", - "osmnx", - "geopandas", - "s3fs", - "dask>=2023.11.0", - "boto3", - "exactextract<=0.2.0.dev252", - "overturemaps", - "scikit-learn>=1.5.1", - "scikit-image>=0.24.0" - ], -) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 8a3b851..36b9973 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages run: | conda update -n base conda - conda install --yes --file .github/setup.py + conda create -f .github/environment.yml conda install gdal conda upgrade numpy - name: Run Tests diff --git a/environment.yml b/environment.yml index a064834..addf333 100644 --- a/environment.yml +++ b/environment.yml @@ -22,7 +22,7 @@ dependencies: - pip=23.3.1 - boto3=1.34.124 - scikit-learn=1.5.1 - - scikit-image==0.24.0 - - exactextract=0.2.0.dev252 + - scikit-image=0.24.0 + - r-exactextractr=0.10.0 - pip: - overturemaps==0.6.0 From c65242f56ce72ed6108954325271346d24a6a746 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 11:58:10 -0700 Subject: [PATCH 16/33] changed conda update --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 36b9973..f66ac9a 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,7 +26,7 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda update -n base conda + conda update conda conda create -f .github/environment.yml conda install gdal conda upgrade numpy From 5dbd0ca4ab33efd4cb816c6c5045d42190326c72 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 12:05:34 -0700 Subject: [PATCH 17/33] further conda tweaks --- .github/workflows/dev_ci_cd_conda.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index f66ac9a..6189cd4 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,7 +26,6 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda update conda conda create -f .github/environment.yml conda install gdal conda upgrade numpy From be6060ba9e91b45dcede289c7aab4d6e082d6d16 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 12:14:26 -0700 Subject: [PATCH 18/33] corrected cond create statement --- .github/workflows/dev_ci_cd_conda.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 6189cd4..b95e5d7 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,9 +26,10 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda create -f .github/environment.yml + conda env create --name env --file=.github/environment.yml conda install gdal conda upgrade numpy + conda active env - name: Run Tests env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} From cebc50a9c2f965536c9d694b3ea60406213c9567 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 12:15:29 -0700 Subject: [PATCH 19/33] corrected cond activation --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index b95e5d7..3f2d770 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -29,7 +29,7 @@ jobs: conda env create --name env --file=.github/environment.yml conda install gdal conda upgrade numpy - conda active env + conda activate env - name: Run Tests env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} From ad5574e28f0704a6a0d3f40cf063e727afc4d615 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 12:47:59 -0700 Subject: [PATCH 20/33] updated environment --- .github/environment.yml | 2 +- .github/workflows/dev_ci_cd_conda.yml | 1 + environment.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/environment.yml b/.github/environment.yml index addf333..7d671ee 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -23,6 +23,6 @@ dependencies: - boto3=1.34.124 - scikit-learn=1.5.1 - scikit-image=0.24.0 - - r-exactextractr=0.10.0 + - exactextract=0.2.0 - pip: - overturemaps==0.6.0 diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 3f2d770..ff2d2dc 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -29,6 +29,7 @@ jobs: conda env create --name env --file=.github/environment.yml conda install gdal conda upgrade numpy + conda init conda activate env - name: Run Tests env: diff --git a/environment.yml b/environment.yml index addf333..7d671ee 100644 --- a/environment.yml +++ b/environment.yml @@ -23,6 +23,6 @@ dependencies: - boto3=1.34.124 - scikit-learn=1.5.1 - scikit-image=0.24.0 - - r-exactextractr=0.10.0 + - exactextract=0.2.0 - pip: - overturemaps==0.6.0 From 7653048d3c1d32bdbe05e2919329701e7bb01b9c Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 12:59:39 -0700 Subject: [PATCH 21/33] tweak yml --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index ff2d2dc..4c87ae8 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -29,7 +29,7 @@ jobs: conda env create --name env --file=.github/environment.yml conda install gdal conda upgrade numpy - conda init + conda init -q conda activate env - name: Run Tests env: From 99c62ab200c4b19cab6c0a3e1ceaa1a7a2e044b1 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:09:22 -0700 Subject: [PATCH 22/33] sourcing bashrc --- .github/workflows/dev_ci_cd_conda.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 4c87ae8..2936b48 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -29,6 +29,7 @@ jobs: conda env create --name env --file=.github/environment.yml conda install gdal conda upgrade numpy + source ~/.bashrc conda init -q conda activate env - name: Run Tests From 0c5c888c26e005a6b6940905a538be678e87b892 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:14:28 -0700 Subject: [PATCH 23/33] adding yes clauses --- .github/workflows/dev_ci_cd_conda.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 2936b48..dbc17bd 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,9 +26,9 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda env create --name env --file=.github/environment.yml - conda install gdal - conda upgrade numpy + conda env create --name env --file=.github/environment.yml --yes + conda install gdal --yes + conda upgrade numpy --yes source ~/.bashrc conda init -q conda activate env From 8efab9acd4cdef957ec538b81b6d147d3f441775 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:20:07 -0700 Subject: [PATCH 24/33] conda init all --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index dbc17bd..6d70f8f 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -30,7 +30,7 @@ jobs: conda install gdal --yes conda upgrade numpy --yes source ~/.bashrc - conda init -q + conda init --all --quiet conda activate env - name: Run Tests env: From cbebdcc22cb4974f3d9c8f31b59d3f83097bf637 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:25:38 -0700 Subject: [PATCH 25/33] conda init environment --- .github/workflows/dev_ci_cd_conda.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 6d70f8f..e334769 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -19,6 +19,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: + auto-update-conda: true python-version: ${{ matrix.python-version }} - name: Install Linux dependencies run: | @@ -29,7 +30,9 @@ jobs: conda env create --name env --file=.github/environment.yml --yes conda install gdal --yes conda upgrade numpy --yes - source ~/.bashrc + - name: Initialize Environment + shell: bash -el {0} + run: | conda init --all --quiet conda activate env - name: Run Tests From 67b7472af7da800805e417f2709ceca3a8f14ae5 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:35:11 -0700 Subject: [PATCH 26/33] other edits --- .github/workflows/dev_ci_cd_conda.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index e334769..375ef93 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -13,9 +13,18 @@ jobs: max-parallel: 4 matrix: python-version: ["3.10"] - + defaults: + run: + shell: bash -l {0} steps: - uses: actions/checkout@v4 + - uses: conda-incubator/setup-miniconda@v2 + with: + miniconda-version: "latest" + channels: bioconda, conda-forge, defaults + use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly! + auto-update-conda: true + auto-activate-base: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -31,9 +40,8 @@ jobs: conda install gdal --yes conda upgrade numpy --yes - name: Initialize Environment - shell: bash -el {0} run: | - conda init --all --quiet + source ~/.bashrc conda activate env - name: Run Tests env: From 2d5aafa89710bda9d8bfca48eedbed9589be07c1 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:43:25 -0700 Subject: [PATCH 27/33] other --- .github/workflows/dev_ci_cd_conda.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 375ef93..8ca9a08 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -18,13 +18,6 @@ jobs: shell: bash -l {0} steps: - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v2 - with: - miniconda-version: "latest" - channels: bioconda, conda-forge, defaults - use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly! - auto-update-conda: true - auto-activate-base: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -36,13 +29,11 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda env create --name env --file=.github/environment.yml --yes + conda env update --file=.github/environment.yml --name base --yes + - name: Install GDAL + run: | conda install gdal --yes conda upgrade numpy --yes - - name: Initialize Environment - run: | - source ~/.bashrc - conda activate env - name: Run Tests env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} From b28c189133506c7c6978ce121bc5032e926cda02 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:49:53 -0700 Subject: [PATCH 28/33] other --- .github/workflows/dev_ci_cd_conda.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 8ca9a08..143a8c0 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -13,9 +13,6 @@ jobs: max-parallel: 4 matrix: python-version: ["3.10"] - defaults: - run: - shell: bash -l {0} steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From 1371b778c09a9f236b0a9648cb98343423ddb0f4 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 13:54:36 -0700 Subject: [PATCH 29/33] other --- .github/workflows/dev_ci_cd_conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 143a8c0..99f5cad 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,7 +26,7 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages run: | - conda env update --file=.github/environment.yml --name base --yes + conda env update --file=.github/environment.yml --name base - name: Install GDAL run: | conda install gdal --yes From 6f7250e160e9b03f4925655597553287e7df21a9 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 14:01:33 -0700 Subject: [PATCH 30/33] install pytest --- .github/workflows/dev_ci_cd_conda.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 99f5cad..8fee512 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -36,4 +36,5 @@ jobs: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} run: | + conda install pytest pytest tests From cf5d98120e073241999a8e2fd67aaebc3cc8acc9 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 14:07:21 -0700 Subject: [PATCH 31/33] install pytest --- .github/workflows/dev_ci_cd_conda.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 8fee512..21f72c3 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -27,14 +27,14 @@ jobs: - name: Install Packages run: | conda env update --file=.github/environment.yml --name base - - name: Install GDAL + - name: Install other packages run: | conda install gdal --yes conda upgrade numpy --yes + conda install pytest --yes - name: Run Tests env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} run: | - conda install pytest pytest tests From a39276c6b4033f32eedead013af8c0a620f40874 Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 14:13:56 -0700 Subject: [PATCH 32/33] specify bash --- .github/workflows/dev_ci_cd_conda.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index 21f72c3..cd3704f 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -24,7 +24,7 @@ jobs: run: | sudo apt update sudo apt install -y gdal-bin libgdal-dev - - name: Install Packages + - name: Install Packages in environment.yml file run: | conda env update --file=.github/environment.yml --name base - name: Install other packages @@ -33,6 +33,7 @@ jobs: conda upgrade numpy --yes conda install pytest --yes - name: Run Tests + shell: bash -l {0} env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} From 2bc6e2de76bd46caf12c78ff65ab9c8dd0c590fc Mon Sep 17 00:00:00 2001 From: Kenn Cartier Date: Thu, 12 Sep 2024 14:25:19 -0700 Subject: [PATCH 33/33] added conda path --- .github/workflows/dev_ci_cd_conda.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dev_ci_cd_conda.yml b/.github/workflows/dev_ci_cd_conda.yml index cd3704f..496dd2e 100644 --- a/.github/workflows/dev_ci_cd_conda.yml +++ b/.github/workflows/dev_ci_cd_conda.yml @@ -26,16 +26,16 @@ jobs: sudo apt install -y gdal-bin libgdal-dev - name: Install Packages in environment.yml file run: | - conda env update --file=.github/environment.yml --name base + $CONDA/bin/conda env update --file=.github/environment.yml --name base - name: Install other packages run: | - conda install gdal --yes - conda upgrade numpy --yes - conda install pytest --yes + $CONDA/bin/conda install gdal --yes + $CONDA/bin/conda upgrade numpy --yes + $CONDA/bin/conda install pytest --yes - name: Run Tests shell: bash -l {0} env: GOOGLE_APPLICATION_USER: ${{ secrets.GOOGLE_APPLICATION_USER }} GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} run: | - pytest tests + $CONDA/bin/pytest tests